tiny.chant.preprocess.embed
Defined in preprocess.
API (4)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: lib/chant/src/preprocess/embed.zig
zig
const std = @import("std");const sys = @import("sys");const types = @import("types.zig");const Error = types.Error;const Options = types.Options;const max_resource_bytes = 16 * 1024 * 1024;const max_include_depth = 64;const sentinel_prefix = "__CHANT_EMBED_";const Status = enum(u8) { not_found = 0, found = 1, empty = 2,};const Params = struct { limit: ?usize = null, prefix: ?[]const u8 = null, suffix: ?[]const u8 = null, if_empty: ?[]const u8 = null,};const Request = struct { name: []const u8, quoted: bool, params: Params,};const Embed = struct { raw: []const u8, source_dir: []const u8,};const Directive = struct { name: []const u8, argument: []const u8,};const Macro = struct { name: []const u8, replacement: []const u8, params: []const []const u8 = &.{}, function_like: bool = false, variadic: bool = false,};const Include = struct { name: []const u8, quoted: bool,};const Included = struct { path: []const u8, dir: []const u8, source: []const u8,};const Condition = struct { parent_active: bool, active: bool, branch_taken: bool,};pub const Expander = struct { arena: std.mem.Allocator, source_path: []const u8, source_dir: []const u8, options: Options, embeds: std.ArrayListUnmanaged(Embed) = .empty, macros: std.ArrayListUnmanaged(Macro) = .empty, conditions: std.ArrayListUnmanaged(Condition) = .empty, changed: bool = false, pub fn init(arena: std.mem.Allocator, source_path: []const u8, options: Options) Error!Expander { return .{ .arena = arena, .source_path = source_path, .source_dir = sourceDirectory(arena, source_path) catch return error.OutOfMemory, .options = options, }; } pub fn prepare(self: *Expander, source: []const u8) Error![]const u8 { const body = try self.prepareSource(self.source_path, self.source_dir, source, 0); if (!self.changed) return source; var out = std.ArrayListUnmanaged(u8).empty; try out.appendSlice(self.arena, \\#ifndef __STDC_EMBED_NOT_FOUND__ \\#define __STDC_EMBED_NOT_FOUND__ 0 \\#endif \\#ifndef __STDC_EMBED_FOUND__ \\#define __STDC_EMBED_FOUND__ 1 \\#endif \\#ifndef __STDC_EMBED_EMPTY__ \\#define __STDC_EMBED_EMPTY__ 2 \\#endif \\ ); try appendLineMarker(self.arena, &out, self.source_path); try out.appendSlice(self.arena, body); return out.toOwnedSlice(self.arena); } fn prepareSource(self: *Expander, source_path: []const u8, source_dir: []const u8, source: []const u8, depth: usize) Error![]const u8 { if (depth > max_include_depth) return error.PreprocessFailed; var body = std.ArrayListUnmanaged(u8).empty; var index: usize = 0; while (index < source.len) { const start = index; while (index < source.len and source[index] != '\n') : (index += 1) {} const line = source[start..index]; const has_newline = index < source.len; if (has_newline) index += 1; var appended = false; if (parseDirective(line)) |direct| { if (try self.appendConditionalDirective(&body, line, direct, source_dir)) { appended = true; } else if (!self.currentActive()) { try body.appendSlice(self.arena, line); appended = true; } else { if (std.mem.eql(u8, direct.name, "define")) { try self.recordDefine(direct.argument); } else if (std.mem.eql(u8, direct.name, "undef")) { self.recordUndef(direct.argument); } else if (std.mem.eql(u8, direct.name, "include")) { if (try self.readInclude(direct.argument, source_dir)) |included| { const prepared = try self.prepareSource(included.path, included.dir, included.source, depth + 1); try self.appendPreparedInclude(&body, included.path, source_path, prepared); appended = true; } } if (!appended and std.mem.eql(u8, direct.name, "embed")) { const id = self.embeds.items.len; const expanded = try self.expandMacros(direct.argument); try self.embeds.append(self.arena, .{ .raw = expanded, .source_dir = source_dir }); self.changed = true; try appendPrint(self.arena, &body, "{s}{d}__", .{ sentinel_prefix, id }); appended = true; } if (!appended and try rewriteHasEmbedDefined(self, &body, direct)) appended = true; } } if (!appended) { if (!try self.appendHasEmbedExpressions(&body, line, source_dir)) { try body.appendSlice(self.arena, line); } } if (has_newline) try body.append(self.arena, '\n'); } return body.toOwnedSlice(self.arena); } fn readInclude(self: *Expander, raw: []const u8, source_dir: []const u8) Error!?Included { const expanded = try self.expandMacros(raw); const include = parseInclude(expanded) orelse return null; const path = self.findInclude(include, source_dir) orelse return null; const included: ?[]const u8 = sys.fs.readFileAlloc(self.arena, path, max_resource_bytes) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => null, }; const source = included orelse return null; const dir = sourceDirectory(self.arena, path) catch return error.OutOfMemory; return .{ .path = path, .dir = dir, .source = source }; } fn appendPreparedInclude(self: *Expander, out: *std.ArrayListUnmanaged(u8), path: []const u8, source_path: []const u8, prepared: []const u8) Error!void { self.changed = true; try appendLineMarker(self.arena, out, path); try out.appendSlice(self.arena, prepared); if (prepared.len == 0 or prepared[prepared.len - 1] != '\n') try out.append(self.arena, '\n'); try appendLineMarker(self.arena, out, source_path); } fn appendConditionalDirective(self: *Expander, out: *std.ArrayListUnmanaged(u8), line: []const u8, direct: Directive, source_dir: []const u8) Error!bool { if (std.mem.eql(u8, direct.name, "if")) { const parent_active = self.currentActive(); const condition = try self.evalCondition(direct.argument, source_dir); try self.conditions.append(self.arena, .{ .parent_active = parent_active, .active = parent_active and condition, .branch_taken = condition, }); } else if (std.mem.eql(u8, direct.name, "ifdef")) { const parent_active = self.currentActive(); const condition = self.isMacroDefined(std.mem.trim(u8, direct.argument, " \t\r")); try self.conditions.append(self.arena, .{ .parent_active = parent_active, .active = parent_active and condition, .branch_taken = condition, }); } else if (std.mem.eql(u8, direct.name, "ifndef")) { const parent_active = self.currentActive(); const condition = !self.isMacroDefined(std.mem.trim(u8, direct.argument, " \t\r")); try self.conditions.append(self.arena, .{ .parent_active = parent_active, .active = parent_active and condition, .branch_taken = condition, }); } else if (std.mem.eql(u8, direct.name, "elif")) { if (self.conditions.items.len > 0) { const condition = try self.evalCondition(direct.argument, source_dir); const top = &self.conditions.items[self.conditions.items.len - 1]; top.active = top.parent_active and !top.branch_taken and condition; top.branch_taken = top.branch_taken or condition; } } else if (std.mem.eql(u8, direct.name, "elifdef")) { if (self.conditions.items.len > 0) { const condition = self.isMacroDefined(std.mem.trim(u8, direct.argument, " \t\r")); const top = &self.conditions.items[self.conditions.items.len - 1]; top.active = top.parent_active and !top.branch_taken and condition; top.branch_taken = top.branch_taken or condition; } } else if (std.mem.eql(u8, direct.name, "elifndef")) { if (self.conditions.items.len > 0) { const condition = !self.isMacroDefined(std.mem.trim(u8, direct.argument, " \t\r")); const top = &self.conditions.items[self.conditions.items.len - 1]; top.active = top.parent_active and !top.branch_taken and condition; top.branch_taken = top.branch_taken or condition; } } else if (std.mem.eql(u8, direct.name, "else")) { if (self.conditions.items.len > 0) { const top = &self.conditions.items[self.conditions.items.len - 1]; top.active = top.parent_active and !top.branch_taken; top.branch_taken = true; } } else if (std.mem.eql(u8, direct.name, "endif")) { if (self.conditions.items.len > 0) _ = self.conditions.pop(); } else { return false; } if (try rewriteHasEmbedDefined(self, out, direct)) return true; if (try self.appendHasEmbedExpressions(out, line, source_dir)) return true; try out.appendSlice(self.arena, line); return true; } fn currentActive(self: *const Expander) bool { if (self.conditions.items.len == 0) return true; return self.conditions.items[self.conditions.items.len - 1].active; } fn appendHasEmbedExpressions(self: *Expander, out: *std.ArrayListUnmanaged(u8), line: []const u8, source_dir: []const u8) Error!bool { var replaced = false; const expanded = try self.expandHasEmbedExpressions(line, source_dir, &replaced); if (!replaced) return false; try out.appendSlice(self.arena, expanded); self.changed = true; return true; } fn expandHasEmbedExpressions(self: *Expander, line: []const u8, source_dir: []const u8, replaced: *bool) Error![]const u8 { var index: usize = 0; var out = std.ArrayListUnmanaged(u8).empty; while (index < line.len) { if (line[index] == '"') { const end = findStringEnd(line, index) orelse line.len - 1; try out.appendSlice(self.arena, line[index .. end + 1]); index = end + 1; continue; } if (line[index] == '\'') { const end = findQuotedEnd(line, index, '\'') orelse line.len - 1; try out.appendSlice(self.arena, line[index .. end + 1]); index = end + 1; continue; } if (!isIdentStart(line[index])) { try out.append(self.arena, line[index]); index += 1; continue; } const start = index; index += 1; while (index < line.len and isIdentContinue(line[index])) : (index += 1) {} const name = line[start..index]; if (!std.mem.eql(u8, name, "__has_embed")) { try out.appendSlice(self.arena, name); continue; } const call = skipHorizontal(line, index); if (call >= line.len or line[call] != '(') { try out.appendSlice(self.arena, name); continue; } const end = findBalancedEnd(line, call) orelse { try out.appendSlice(self.arena, line[start..]); return out.toOwnedSlice(self.arena); }; try appendPrint(self.arena, &out, "{d}", .{@backingInt(try self.hasEmbed(line[call + 1 .. end], source_dir))}); replaced.* = true; index = end + 1; } return out.toOwnedSlice(self.arena); } fn hasEmbed(self: *Expander, raw: []const u8, source_dir: []const u8) Error!Status { const expanded = try self.expandMacros(raw); const request = parseRequest(expanded) orelse return .not_found; const path = self.findResource(request, source_dir) orelse return .not_found; const resource = sys.fs.readFileAlloc(self.arena, path, max_resource_bytes) catch return .not_found; const count = if (request.params.limit) |limit| @min(limit, resource.len) else resource.len; return if (count == 0) .empty else .found; } pub fn expandOutput(self: *Expander, preprocessed: []const u8) Error![]const u8 { if (self.embeds.items.len == 0) return preprocessed; var out = std.ArrayListUnmanaged(u8).empty; var index: usize = 0; while (index < preprocessed.len) { if (std.mem.startsWith(u8, preprocessed[index..], sentinel_prefix)) { var cursor = index + sentinel_prefix.len; const digits_start = cursor; while (cursor < preprocessed.len and std.ascii.isDigit(preprocessed[cursor])) : (cursor += 1) {} if (cursor > digits_start and cursor + 2 <= preprocessed.len and std.mem.eql(u8, preprocessed[cursor .. cursor + 2], "__")) { const id = std.fmt.parseInt(usize, preprocessed[digits_start..cursor], 10) catch null; if (id) |embed_id| { if (embed_id < self.embeds.items.len) { const expanded = try self.expandEmbed(self.embeds.items[embed_id]); try out.appendSlice(self.arena, expanded); index = cursor + 2; continue; } } } } try out.append(self.arena, preprocessed[index]); index += 1; } return out.toOwnedSlice(self.arena); } fn expandEmbed(self: *Expander, embed: Embed) Error![]const u8 { const request = parseRequest(embed.raw) orelse return error.PreprocessFailed; const path = self.findResource(request, embed.source_dir) orelse return error.PreprocessFailed; const resource = sys.fs.readFileAlloc(self.arena, path, max_resource_bytes) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.PreprocessFailed, }; const count = if (request.params.limit) |limit| @min(limit, resource.len) else resource.len; if (count == 0) return request.params.if_empty orelse ""; var out = std.ArrayListUnmanaged(u8).empty; if (request.params.prefix) |prefix| try out.appendSlice(self.arena, prefix); for (resource[0..count], 0..) |byte, byte_index| { if (byte_index != 0) try out.append(self.arena, ','); try appendPrint(self.arena, &out, "{d}", .{byte}); } if (request.params.suffix) |suffix| try out.appendSlice(self.arena, suffix); return out.toOwnedSlice(self.arena); } fn findResource(self: *Expander, request: Request, source_dir: []const u8) ?[]const u8 { if (std.fs.path.isAbsolute(request.name) and fileExists(request.name)) return request.name; if (request.quoted) { if (self.findResourceInDir(source_dir, request.name)) |path| return path; if (self.findResourceInDir(".", request.name)) |path| return path; } for (self.options.include_dirs) |dir| { if (self.findResourceInDir(dir, request.name)) |path| return path; } if (!request.quoted) { if (self.findResourceInDir(source_dir, request.name)) |path| return path; if (self.findResourceInDir(".", request.name)) |path| return path; } return null; } fn findInclude(self: *Expander, include: Include, source_dir: []const u8) ?[]const u8 { if (std.fs.path.isAbsolute(include.name) and fileExists(include.name)) return include.name; if (include.quoted) { if (self.findResourceInDir(source_dir, include.name)) |path| return path; if (self.findResourceInDir(".", include.name)) |path| return path; } for (self.options.include_dirs) |dir| { if (self.findResourceInDir(dir, include.name)) |path| return path; } if (!include.quoted) { if (self.findResourceInDir(source_dir, include.name)) |path| return path; if (self.findResourceInDir(".", include.name)) |path| return path; } return null; } fn findResourceInDir(self: *Expander, dir: []const u8, name: []const u8) ?[]const u8 { const path = std.fs.path.join(self.arena, &.{ dir, name }) catch return null; if (fileExists(path)) return path; return null; } fn recordDefine(self: *Expander, raw: []const u8) Error!void { const macro = (try parseDefine(self.arena, raw)) orelse return; try self.macros.append(self.arena, macro); } fn recordUndef(self: *Expander, raw: []const u8) void { const name = parseUndef(raw) orelse return; var index: usize = 0; while (index < self.macros.items.len) { if (std.mem.eql(u8, self.macros.items[index].name, name)) { _ = self.macros.orderedRemove(index); continue; } index += 1; } } fn expandMacros(self: *Expander, raw: []const u8) Error![]const u8 { var current = raw; var pass: usize = 0; while (pass < 16) : (pass += 1) { var changed = false; const expanded = try self.expandMacrosOnce(current, &changed); if (!changed) return current; current = expanded; } return current; } fn expandMacrosOnce(self: *Expander, raw: []const u8, changed: *bool) Error![]const u8 { var out = std.ArrayListUnmanaged(u8).empty; var index: usize = 0; while (index < raw.len) { if (raw[index] == '"') { const end = findStringEnd(raw, index) orelse raw.len - 1; try out.appendSlice(self.arena, raw[index .. end + 1]); index = end + 1; continue; } if (raw[index] == '\'') { const end = findQuotedEnd(raw, index, '\'') orelse raw.len - 1; try out.appendSlice(self.arena, raw[index .. end + 1]); index = end + 1; continue; } if (isIdentStart(raw[index])) { const start = index; index += 1; while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {} const name = raw[start..index]; if (self.lookupMacro(name)) |macro| { if (!macro.function_like) { try out.appendSlice(self.arena, macro.replacement); changed.* = true; } else { const call = skipHorizontal(raw, index); if (call >= raw.len or raw[call] != '(') { try out.appendSlice(self.arena, name); } else if (findBalancedEnd(raw, call)) |end| { if (try self.expandFunctionMacro(macro, raw[call + 1 .. end])) |expanded| { try out.appendSlice(self.arena, expanded); changed.* = true; index = end + 1; } else { try out.appendSlice(self.arena, raw[start .. end + 1]); index = end + 1; } } else { try out.appendSlice(self.arena, name); } } } else { try out.appendSlice(self.arena, name); } continue; } try out.append(self.arena, raw[index]); index += 1; } return out.toOwnedSlice(self.arena); } fn expandFunctionMacro(self: *Expander, macro: Macro, raw_arguments: []const u8) Error!?[]const u8 { const arguments = try self.parseMacroArguments(macro, raw_arguments) orelse return null; var expanded = try self.arena.alloc([]const u8, arguments.len); for (arguments, 0..) |argument, argument_index| { expanded[argument_index] = try self.expandMacros(argument); } return try self.substituteFunctionMacro(macro, expanded); } fn parseMacroArguments(self: *Expander, macro: Macro, raw: []const u8) Error!?[]const []const u8 { var pieces = std.ArrayListUnmanaged([]const u8).empty; const trimmed = std.mem.trim(u8, raw, " \t\r"); if (trimmed.len == 0) { if (macro.params.len == 0) return &.{}; try pieces.append(self.arena, ""); } else { var start: usize = 0; var index: usize = 0; var depth: usize = 0; while (index < raw.len) : (index += 1) { switch (raw[index]) { '"' => index = findStringEnd(raw, index) orelse return null, '\'' => index = findQuotedEnd(raw, index, '\'') orelse return null, '(' => depth += 1, ')' => { if (depth > 0) depth -= 1; }, ',' => if (depth == 0) { try pieces.append(self.arena, std.mem.trim(u8, raw[start..index], " \t\r")); start = index + 1; }, else => {}, } } try pieces.append(self.arena, std.mem.trim(u8, raw[start..], " \t\r")); } if (!macro.variadic) { if (pieces.items.len != macro.params.len) return null; return try pieces.toOwnedSlice(self.arena); } const fixed_count = macro.params.len - 1; if (pieces.items.len < fixed_count) return null; var arguments = try self.arena.alloc([]const u8, macro.params.len); for (arguments[0..fixed_count], 0..) |*argument, argument_index| argument.* = pieces.items[argument_index]; if (pieces.items.len == fixed_count) { arguments[fixed_count] = ""; } else if (pieces.items.len == fixed_count + 1) { arguments[fixed_count] = pieces.items[fixed_count]; } else { var joined = std.ArrayListUnmanaged(u8).empty; for (pieces.items[fixed_count..], 0..) |piece, piece_index| { if (piece_index != 0) try joined.append(self.arena, ','); try joined.appendSlice(self.arena, piece); } arguments[fixed_count] = try joined.toOwnedSlice(self.arena); } return arguments; } fn substituteFunctionMacro(self: *Expander, macro: Macro, arguments: []const []const u8) Error![]const u8 { var out = std.ArrayListUnmanaged(u8).empty; var index: usize = 0; while (index < macro.replacement.len) { if (macro.replacement[index] == '"') { const end = findStringEnd(macro.replacement, index) orelse macro.replacement.len - 1; try out.appendSlice(self.arena, macro.replacement[index .. end + 1]); index = end + 1; continue; } if (macro.replacement[index] == '\'') { const end = findQuotedEnd(macro.replacement, index, '\'') orelse macro.replacement.len - 1; try out.appendSlice(self.arena, macro.replacement[index .. end + 1]); index = end + 1; continue; } if (!isIdentStart(macro.replacement[index])) { try out.append(self.arena, macro.replacement[index]); index += 1; continue; } const start = index; index += 1; while (index < macro.replacement.len and isIdentContinue(macro.replacement[index])) : (index += 1) {} const name = macro.replacement[start..index]; if (macro.variadic and std.mem.eql(u8, name, "__VA_OPT__")) { const call = skipHorizontal(macro.replacement, index); if (call < macro.replacement.len and macro.replacement[call] == '(') { if (findBalancedEnd(macro.replacement, call)) |end| { if (std.mem.trim(u8, arguments[arguments.len - 1], " \t\r").len != 0) { try out.appendSlice(self.arena, macro.replacement[call + 1 .. end]); } index = end + 1; continue; } } } if (paramIndex(macro, name)) |argument_index| { try out.appendSlice(self.arena, arguments[argument_index]); } else { try out.appendSlice(self.arena, name); } } return out.toOwnedSlice(self.arena); } fn lookupMacro(self: *const Expander, name: []const u8) ?Macro { var index = self.macros.items.len; while (index > 0) { index -= 1; const macro = self.macros.items[index]; if (std.mem.eql(u8, macro.name, name)) return macro; } if (builtinMacro(name)) |replacement| return .{ .name = name, .replacement = replacement }; return null; } fn isMacroDefined(self: *const Expander, name: []const u8) bool { if (std.mem.eql(u8, name, "__has_embed")) return true; return self.lookupMacro(name) != null; } fn evalCondition(self: *Expander, raw: []const u8, source_dir: []const u8) Error!bool { var embed_replaced = false; const with_embed = try self.expandHasEmbedExpressions(raw, source_dir, &embed_replaced); const with_defined = try self.replaceDefinedOperators(with_embed); const expanded = try self.expandMacros(with_defined); var parser = ConditionParser{ .text = expanded }; const value = parser.parse() orelse return true; return value != 0; } fn replaceDefinedOperators(self: *Expander, raw: []const u8) Error![]const u8 { var out = std.ArrayListUnmanaged(u8).empty; var index: usize = 0; while (index < raw.len) { if (raw[index] == '"') { const end = findStringEnd(raw, index) orelse raw.len - 1; try out.appendSlice(self.arena, raw[index .. end + 1]); index = end + 1; continue; } if (raw[index] == '\'') { const end = findQuotedEnd(raw, index, '\'') orelse raw.len - 1; try out.appendSlice(self.arena, raw[index .. end + 1]); index = end + 1; continue; } if (!isIdentStart(raw[index])) { try out.append(self.arena, raw[index]); index += 1; continue; } const start = index; index += 1; while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {} const name = raw[start..index]; if (!std.mem.eql(u8, name, "defined")) { try out.appendSlice(self.arena, name); continue; } var cursor = skipHorizontal(raw, index); var parenthesized = false; if (cursor < raw.len and raw[cursor] == '(') { parenthesized = true; cursor = skipHorizontal(raw, cursor + 1); } if (cursor >= raw.len or !isIdentStart(raw[cursor])) { try out.appendSlice(self.arena, name); continue; } const macro_start = cursor; cursor += 1; while (cursor < raw.len and isIdentContinue(raw[cursor])) : (cursor += 1) {} const macro_name = raw[macro_start..cursor]; cursor = skipHorizontal(raw, cursor); if (parenthesized) { if (cursor >= raw.len or raw[cursor] != ')') { try out.appendSlice(self.arena, name); continue; } cursor += 1; } try out.append(self.arena, if (self.isMacroDefined(macro_name)) '1' else '0'); index = cursor; } return out.toOwnedSlice(self.arena); }};const ConditionParser = struct { text: []const u8, index: usize = 0, fn parse(self: *ConditionParser) ?i128 { const value = self.parseOr() orelse return null; self.skip(); if (self.index != self.text.len) return null; return value; } fn parseOr(self: *ConditionParser) ?i128 { var value = self.parseAnd() orelse return null; while (self.consume("||")) { const right = self.parseAnd() orelse return null; value = if (value != 0 or right != 0) 1 else 0; } return value; } fn parseAnd(self: *ConditionParser) ?i128 { var value = self.parseBitOr() orelse return null; while (self.consume("&&")) { const right = self.parseBitOr() orelse return null; value = if (value != 0 and right != 0) 1 else 0; } return value; } fn parseBitOr(self: *ConditionParser) ?i128 { var value = self.parseBitXor() orelse return null; while (self.consumeSingle('|', "||")) { const right = self.parseBitXor() orelse return null; value |= right; } return value; } fn parseBitXor(self: *ConditionParser) ?i128 { var value = self.parseBitAnd() orelse return null; while (self.consume("^")) { const right = self.parseBitAnd() orelse return null; value ^= right; } return value; } fn parseBitAnd(self: *ConditionParser) ?i128 { var value = self.parseEquality() orelse return null; while (self.consumeSingle('&', "&&")) { const right = self.parseEquality() orelse return null; value &= right; } return value; } fn parseEquality(self: *ConditionParser) ?i128 { var value = self.parseRelational() orelse return null; while (true) { if (self.consume("==")) { const right = self.parseRelational() orelse return null; value = if (value == right) 1 else 0; } else if (self.consume("!=")) { const right = self.parseRelational() orelse return null; value = if (value != right) 1 else 0; } else { return value; } } } fn parseRelational(self: *ConditionParser) ?i128 { var value = self.parseShift() orelse return null; while (true) { if (self.consume("<=")) { const right = self.parseShift() orelse return null; value = if (value <= right) 1 else 0; } else if (self.consume(">=")) { const right = self.parseShift() orelse return null; value = if (value >= right) 1 else 0; } else if (self.consume("<")) { const right = self.parseShift() orelse return null; value = if (value < right) 1 else 0; } else if (self.consume(">")) { const right = self.parseShift() orelse return null; value = if (value > right) 1 else 0; } else { return value; } } } fn parseShift(self: *ConditionParser) ?i128 { var value = self.parseAdd() orelse return null; while (true) { if (self.consume("<<")) { const right = self.parseAdd() orelse return null; if (right < 0 or right > 127) return null; value = value << @intCast(right); } else if (self.consume(">>")) { const right = self.parseAdd() orelse return null; if (right < 0 or right > 127) return null; value = value >> @intCast(right); } else { return value; } } } fn parseAdd(self: *ConditionParser) ?i128 { var value = self.parseMul() orelse return null; while (true) { if (self.consume("+")) { const right = self.parseMul() orelse return null; value += right; } else if (self.consume("-")) { const right = self.parseMul() orelse return null; value -= right; } else { return value; } } } fn parseMul(self: *ConditionParser) ?i128 { var value = self.parseUnary() orelse return null; while (true) { if (self.consume("*")) { const right = self.parseUnary() orelse return null; value *= right; } else if (self.consume("/")) { const right = self.parseUnary() orelse return null; if (right == 0) return null; value = @divTrunc(value, right); } else if (self.consume("%")) { const right = self.parseUnary() orelse return null; if (right == 0) return null; value = @rem(value, right); } else { return value; } } } fn parseUnary(self: *ConditionParser) ?i128 { if (self.consume("!")) return if ((self.parseUnary() orelse return null) == 0) 1 else 0; if (self.consume("+")) return self.parseUnary(); if (self.consume("-")) return -(self.parseUnary() orelse return null); if (self.consume("~")) return ~(self.parseUnary() orelse return null); return self.parsePrimary(); } fn parsePrimary(self: *ConditionParser) ?i128 { self.skip(); if (self.index >= self.text.len) return null; if (self.text[self.index] == '(') { self.index += 1; const value = self.parseOr() orelse return null; self.skip(); if (self.index >= self.text.len or self.text[self.index] != ')') return null; self.index += 1; return value; } if (self.text[self.index] == '\'') return self.parseChar(); if (std.ascii.isDigit(self.text[self.index])) return self.parseNumber(); if (isIdentStart(self.text[self.index])) { self.index += 1; while (self.index < self.text.len and isIdentContinue(self.text[self.index])) : (self.index += 1) {} self.skip(); if (self.index < self.text.len and self.text[self.index] == '(') return null; return 0; } return null; } fn parseNumber(self: *ConditionParser) ?i128 { const start = self.index; var base: u8 = 10; if (self.index + 2 <= self.text.len and self.text[self.index] == '0' and (self.text[self.index + 1] == 'x' or self.text[self.index + 1] == 'X')) { base = 16; self.index += 2; } const digits_start = self.index; while (self.index < self.text.len) : (self.index += 1) { if (base == 16) { if (!std.ascii.isHex(self.text[self.index])) break; } else if (!std.ascii.isDigit(self.text[self.index])) { break; } } if (self.index == digits_start) return null; const digits = self.text[digits_start..self.index]; const value = std.fmt.parseInt(i128, digits, base) catch return null; while (self.index < self.text.len and (isIdentContinue(self.text[self.index]) or self.text[self.index] == '\'')) : (self.index += 1) {} _ = start; return value; } fn parseChar(self: *ConditionParser) ?i128 { self.index += 1; if (self.index >= self.text.len) return null; const value: i128 = if (self.text[self.index] == '\\') value: { self.index += 1; if (self.index >= self.text.len) return null; break :value switch (self.text[self.index]) { 'n' => 10, 'r' => 13, 't' => 9, '0' => 0, else => self.text[self.index], }; } else self.text[self.index]; self.index += 1; if (self.index >= self.text.len or self.text[self.index] != '\'') return null; self.index += 1; return value; } fn consume(self: *ConditionParser, token: []const u8) bool { self.skip(); if (!std.mem.startsWith(u8, self.text[self.index..], token)) return false; self.index += token.len; return true; } fn consumeSingle(self: *ConditionParser, token: u8, excluded: []const u8) bool { self.skip(); if (std.mem.startsWith(u8, self.text[self.index..], excluded)) return false; if (self.index >= self.text.len or self.text[self.index] != token) return false; self.index += 1; return true; } fn skip(self: *ConditionParser) void { self.index = skipHorizontal(self.text, self.index); }};fn rewriteHasEmbedDefined(expander: *Expander, out: *std.ArrayListUnmanaged(u8), direct: Directive) Error!bool { const positive = std.mem.eql(u8, direct.name, "ifdef") or std.mem.eql(u8, direct.name, "elifdef"); const negative = std.mem.eql(u8, direct.name, "ifndef") or std.mem.eql(u8, direct.name, "elifndef"); if (!positive and !negative) return false; if (!std.mem.eql(u8, std.mem.trim(u8, direct.argument, " \t\r"), "__has_embed")) return false; const elif = std.mem.startsWith(u8, direct.name, "elif"); try out.appendSlice(expander.arena, if (elif) "#elif " else "#if "); try out.append(expander.arena, if (positive) '1' else '0'); expander.changed = true; return true;}fn parseRequest(raw: []const u8) ?Request { var index = skipHorizontal(raw, 0); if (index >= raw.len) return null; const quoted = switch (raw[index]) { '"' => true, '<' => false, else => return null, }; const end = if (quoted) findStringEnd(raw, index) orelse return null else std.mem.indexOfScalarPos(u8, raw, index + 1, '>') orelse return null; const name = raw[index + 1 .. end]; index = skipHorizontal(raw, end + 1); const params = parseParams(raw[index..]) orelse return null; return .{ .name = name, .quoted = quoted, .params = params };}fn parseInclude(raw: []const u8) ?Include { var index = skipHorizontal(raw, 0); if (index >= raw.len) return null; const quoted = switch (raw[index]) { '"' => true, '<' => false, else => return null, }; const end = if (quoted) findStringEnd(raw, index) orelse return null else std.mem.indexOfScalarPos(u8, raw, index + 1, '>') orelse return null; const name = raw[index + 1 .. end]; index = skipHorizontal(raw, end + 1); if (index != raw.len) return null; return .{ .name = name, .quoted = quoted };}fn parseDefine(arena: std.mem.Allocator, raw: []const u8) Error!?Macro { var index = skipHorizontal(raw, 0); if (index >= raw.len or !isIdentStart(raw[index])) return null; const start = index; index += 1; while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {} const name = raw[start..index]; if (index < raw.len and raw[index] == '(') { index += 1; var params = std.ArrayListUnmanaged([]const u8).empty; var variadic = false; index = skipHorizontal(raw, index); if (index < raw.len and raw[index] == ')') { index += 1; } else { while (index < raw.len) { index = skipHorizontal(raw, index); if (index >= raw.len) return null; if (index + 3 <= raw.len and std.mem.eql(u8, raw[index .. index + 3], "...")) { try params.append(arena, "__VA_ARGS__"); variadic = true; index += 3; index = skipHorizontal(raw, index); if (index >= raw.len or raw[index] != ')') return null; index += 1; break; } if (!isIdentStart(raw[index])) return null; const param_start = index; index += 1; while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {} const param = raw[param_start..index]; for (params.items) |existing| { if (std.mem.eql(u8, existing, param)) return null; } try params.append(arena, param); index = skipHorizontal(raw, index); if (index >= raw.len) return null; if (raw[index] == ',') { index += 1; continue; } if (raw[index] == ')') { index += 1; break; } return null; } } const replacement = std.mem.trim(u8, raw[index..], " \t\r"); return .{ .name = name, .replacement = replacement, .params = try params.toOwnedSlice(arena), .function_like = true, .variadic = variadic, }; } const replacement = std.mem.trim(u8, raw[index..], " \t\r"); return .{ .name = name, .replacement = replacement };}fn paramIndex(macro: Macro, name: []const u8) ?usize { for (macro.params, 0..) |param, index| { if (std.mem.eql(u8, param, name)) return index; } return null;}fn builtinMacro(name: []const u8) ?[]const u8 { if (std.mem.eql(u8, name, "__STDC_EMBED_NOT_FOUND__")) return "0"; if (std.mem.eql(u8, name, "__STDC_EMBED_FOUND__")) return "1"; if (std.mem.eql(u8, name, "__STDC_EMBED_EMPTY__")) return "2"; return null;}fn parseUndef(raw: []const u8) ?[]const u8 { var index = skipHorizontal(raw, 0); if (index >= raw.len or !isIdentStart(raw[index])) return null; const start = index; index += 1; while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {} const name = raw[start..index]; index = skipHorizontal(raw, index); if (index != raw.len) return null; return name;}fn parseParams(raw: []const u8) ?Params { var params: Params = .{}; var index: usize = 0; while (true) { index = skipHorizontal(raw, index); if (index >= raw.len) return params; const name_start = index; if (!isIdentStart(raw[index])) return null; index += 1; while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {} const name = raw[name_start..index]; index = skipHorizontal(raw, index); if (index >= raw.len or raw[index] != '(') return null; const end = findBalancedEnd(raw, index) orelse return null; const body = std.mem.trim(u8, raw[index + 1 .. end], " \t\r"); if (std.mem.eql(u8, name, "limit")) { if (params.limit != null) return null; params.limit = parseLimit(body) orelse return null; } else if (std.mem.eql(u8, name, "prefix")) { if (params.prefix != null) return null; params.prefix = body; } else if (std.mem.eql(u8, name, "suffix")) { if (params.suffix != null) return null; params.suffix = body; } else if (std.mem.eql(u8, name, "if_empty")) { if (params.if_empty != null) return null; params.if_empty = body; } else { return null; } index = end + 1; }}fn parseLimit(raw: []const u8) ?usize { if (raw.len == 0) return null; if (std.mem.startsWith(u8, raw, "0x") or std.mem.startsWith(u8, raw, "0X")) return std.fmt.parseInt(usize, raw[2..], 16) catch null; return std.fmt.parseInt(usize, raw, 10) catch null;}fn parseDirective(line: []const u8) ?Directive { var index = skipHorizontal(line, 0); if (index >= line.len or line[index] != '#') return null; index = skipHorizontal(line, index + 1); if (index >= line.len or !isIdentStart(line[index])) return null; const start = index; index += 1; while (index < line.len and isIdentContinue(line[index])) : (index += 1) {} return .{ .name = line[start..index], .argument = line[index..] };}fn findStringEnd(raw: []const u8, start: usize) ?usize { return findQuotedEnd(raw, start, '"');}fn findQuotedEnd(raw: []const u8, start: usize, quote: u8) ?usize { var index = start + 1; while (index < raw.len) : (index += 1) { if (raw[index] == '\\') { index += 1; continue; } if (raw[index] == quote) return index; } return null;}fn findBalancedEnd(raw: []const u8, start: usize) ?usize { var depth: usize = 0; var index = start; while (index < raw.len) : (index += 1) { switch (raw[index]) { '"' => index = findStringEnd(raw, index) orelse return null, '(' => depth += 1, ')' => { depth -= 1; if (depth == 0) return index; }, else => {}, } } return null;}fn appendLineMarker(arena: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), path: []const u8) Error!void { try out.appendSlice(arena, "#line 1 \""); for (path) |byte| { if (byte == '"' or byte == '\\') try out.append(arena, '\\'); try out.append(arena, byte); } try out.appendSlice(arena, "\"\n");}fn appendPrint( arena: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), comptime fmt: []const u8, args: anytype,) Error!void { const text = std.fmt.allocPrint(arena, fmt, args) catch return error.OutOfMemory; try out.appendSlice(arena, text);}fn sourceDirectory(arena: std.mem.Allocator, source_path: []const u8) ![]const u8 { const dir = std.fs.path.dirname(source_path) orelse "."; return std.fs.path.resolve(arena, &.{dir});}fn fileExists(path: []const u8) bool { sys.fs.cwd().access(sys.fs.debugIo(), path, .{}) catch return false; return true;}fn skipHorizontal(text: []const u8, start: usize) usize { var index = start; while (index < text.len and isHorizontal(text[index])) : (index += 1) {} return index;}fn isHorizontal(byte: u8) bool { return byte == ' ' or byte == '\t' or byte == '\r';}fn isIdentStart(byte: u8) bool { return std.ascii.isAlphabetic(byte) or byte == '_';}fn isIdentContinue(byte: u8) bool { return isIdentStart(byte) or std.ascii.isDigit(byte);}Source: lib/chant/src/preprocess/root.zig:4
zig
pub const embed = @import("embed.zig");Audit
| Definitions | 5 |
|---|---|
| Public names | 5 |
| Members | 8 |
| Version | 26.7.0 |
| Revision | daab053ee433 |