lib/chant/src/preprocess/embed.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sys = @import("sys");
3 const types = @import("types.zig");
4
5 const Error = types.Error;
6 const Options = types.Options;
7
8 const max_resource_bytes = 16 * 1024 * 1024;
9 const max_include_depth = 64;
10 const sentinel_prefix = "__CHANT_EMBED_";
11
12 const Status = enum(u8) {
13 not_found = 0,
14 found = 1,
15 empty = 2,
16 };
17
18 const Params = struct {
19 limit: ?usize = null,
20 prefix: ?[]const u8 = null,
21 suffix: ?[]const u8 = null,
22 if_empty: ?[]const u8 = null,
23 };
24
25 const Request = struct {
26 name: []const u8,
27 quoted: bool,
28 params: Params,
29 };
30
31 const Embed = struct {
32 raw: []const u8,
33 source_dir: []const u8,
34 };
35
36 const Directive = struct {
37 name: []const u8,
38 argument: []const u8,
39 };
40
41 const Macro = struct {
42 name: []const u8,
43 replacement: []const u8,
44 params: []const []const u8 = &.{},
45 function_like: bool = false,
46 variadic: bool = false,
47 };
48
49 const Include = struct {
50 name: []const u8,
51 quoted: bool,
52 };
53
54 const Included = struct {
55 path: []const u8,
56 dir: []const u8,
57 source: []const u8,
58 };
59
60 const Condition = struct {
61 parent_active: bool,
62 active: bool,
63 branch_taken: bool,
64 };
65
66 pub const Expander = struct {
67 arena: std.mem.Allocator,
68 source_path: []const u8,
69 source_dir: []const u8,
70 options: Options,
71 embeds: std.ArrayListUnmanaged(Embed) = .empty,
72 macros: std.ArrayListUnmanaged(Macro) = .empty,
73 conditions: std.ArrayListUnmanaged(Condition) = .empty,
74 changed: bool = false,
75
76 pub fn init(arena: std.mem.Allocator, source_path: []const u8, options: Options) Error!Expander {
77 return .{
78 .arena = arena,
79 .source_path = source_path,
80 .source_dir = sourceDirectory(arena, source_path) catch return error.OutOfMemory,
81 .options = options,
82 };
83 }
84
85 pub fn prepare(self: *Expander, source: []const u8) Error![]const u8 {
86 const body = try self.prepareSource(self.source_path, self.source_dir, source, 0);
87 if (!self.changed) return source;
88
89 var out = std.ArrayListUnmanaged(u8).empty;
90 try out.appendSlice(self.arena,
91 \\#ifndef __STDC_EMBED_NOT_FOUND__
92 \\#define __STDC_EMBED_NOT_FOUND__ 0
93 \\#endif
94 \\#ifndef __STDC_EMBED_FOUND__
95 \\#define __STDC_EMBED_FOUND__ 1
96 \\#endif
97 \\#ifndef __STDC_EMBED_EMPTY__
98 \\#define __STDC_EMBED_EMPTY__ 2
99 \\#endif
100 \\
101 );
102 try appendLineMarker(self.arena, &out, self.source_path);
103 try out.appendSlice(self.arena, body);
104 return out.toOwnedSlice(self.arena);
105 }
106
107 fn prepareSource(self: *Expander, source_path: []const u8, source_dir: []const u8, source: []const u8, depth: usize) Error![]const u8 {
108 if (depth > max_include_depth) return error.PreprocessFailed;
109 var body = std.ArrayListUnmanaged(u8).empty;
110 var index: usize = 0;
111 while (index < source.len) {
112 const start = index;
113 while (index < source.len and source[index] != '\n') : (index += 1) {}
114 const line = source[start..index];
115 const has_newline = index < source.len;
116 if (has_newline) index += 1;
117 var appended = false;
118 if (parseDirective(line)) |direct| {
119 if (try self.appendConditionalDirective(&body, line, direct, source_dir)) {
120 appended = true;
121 } else if (!self.currentActive()) {
122 try body.appendSlice(self.arena, line);
123 appended = true;
124 } else {
125 if (std.mem.eql(u8, direct.name, "define")) {
126 try self.recordDefine(direct.argument);
127 } else if (std.mem.eql(u8, direct.name, "undef")) {
128 self.recordUndef(direct.argument);
129 } else if (std.mem.eql(u8, direct.name, "include")) {
130 if (try self.readInclude(direct.argument, source_dir)) |included| {
131 const prepared = try self.prepareSource(included.path, included.dir, included.source, depth + 1);
132 try self.appendPreparedInclude(&body, included.path, source_path, prepared);
133 appended = true;
134 }
135 }
136 if (!appended and std.mem.eql(u8, direct.name, "embed")) {
137 const id = self.embeds.items.len;
138 const expanded = try self.expandMacros(direct.argument);
139 try self.embeds.append(self.arena, .{ .raw = expanded, .source_dir = source_dir });
140 self.changed = true;
141 try appendPrint(self.arena, &body, "{s}{d}__", .{ sentinel_prefix, id });
142 appended = true;
143 }
144 if (!appended and try rewriteHasEmbedDefined(self, &body, direct)) appended = true;
145 }
146 }
147 if (!appended) {
148 if (!try self.appendHasEmbedExpressions(&body, line, source_dir)) {
149 try body.appendSlice(self.arena, line);
150 }
151 }
152 if (has_newline) try body.append(self.arena, '\n');
153 }
154 return body.toOwnedSlice(self.arena);
155 }
156
157 fn readInclude(self: *Expander, raw: []const u8, source_dir: []const u8) Error!?Included {
158 const expanded = try self.expandMacros(raw);
159 const include = parseInclude(expanded) orelse return null;
160 const path = self.findInclude(include, source_dir) orelse return null;
161 const included: ?[]const u8 = sys.fs.readFileAlloc(self.arena, path, max_resource_bytes) catch |err| switch (err) {
162 error.OutOfMemory => return error.OutOfMemory,
163 else => null,
164 };
165 const source = included orelse return null;
166 const dir = sourceDirectory(self.arena, path) catch return error.OutOfMemory;
167 return .{ .path = path, .dir = dir, .source = source };
168 }
169
170 fn appendPreparedInclude(self: *Expander, out: *std.ArrayListUnmanaged(u8), path: []const u8, source_path: []const u8, prepared: []const u8) Error!void {
171 self.changed = true;
172 try appendLineMarker(self.arena, out, path);
173 try out.appendSlice(self.arena, prepared);
174 if (prepared.len == 0 or prepared[prepared.len - 1] != '\n') try out.append(self.arena, '\n');
175 try appendLineMarker(self.arena, out, source_path);
176 }
177
178 fn appendConditionalDirective(self: *Expander, out: *std.ArrayListUnmanaged(u8), line: []const u8, direct: Directive, source_dir: []const u8) Error!bool {
179 if (std.mem.eql(u8, direct.name, "if")) {
180 const parent_active = self.currentActive();
181 const condition = try self.evalCondition(direct.argument, source_dir);
182 try self.conditions.append(self.arena, .{
183 .parent_active = parent_active,
184 .active = parent_active and condition,
185 .branch_taken = condition,
186 });
187 } else if (std.mem.eql(u8, direct.name, "ifdef")) {
188 const parent_active = self.currentActive();
189 const condition = self.isMacroDefined(std.mem.trim(u8, direct.argument, " \t\r"));
190 try self.conditions.append(self.arena, .{
191 .parent_active = parent_active,
192 .active = parent_active and condition,
193 .branch_taken = condition,
194 });
195 } else if (std.mem.eql(u8, direct.name, "ifndef")) {
196 const parent_active = self.currentActive();
197 const condition = !self.isMacroDefined(std.mem.trim(u8, direct.argument, " \t\r"));
198 try self.conditions.append(self.arena, .{
199 .parent_active = parent_active,
200 .active = parent_active and condition,
201 .branch_taken = condition,
202 });
203 } else if (std.mem.eql(u8, direct.name, "elif")) {
204 if (self.conditions.items.len > 0) {
205 const condition = try self.evalCondition(direct.argument, source_dir);
206 const top = &self.conditions.items[self.conditions.items.len - 1];
207 top.active = top.parent_active and !top.branch_taken and condition;
208 top.branch_taken = top.branch_taken or condition;
209 }
210 } else if (std.mem.eql(u8, direct.name, "elifdef")) {
211 if (self.conditions.items.len > 0) {
212 const condition = self.isMacroDefined(std.mem.trim(u8, direct.argument, " \t\r"));
213 const top = &self.conditions.items[self.conditions.items.len - 1];
214 top.active = top.parent_active and !top.branch_taken and condition;
215 top.branch_taken = top.branch_taken or condition;
216 }
217 } else if (std.mem.eql(u8, direct.name, "elifndef")) {
218 if (self.conditions.items.len > 0) {
219 const condition = !self.isMacroDefined(std.mem.trim(u8, direct.argument, " \t\r"));
220 const top = &self.conditions.items[self.conditions.items.len - 1];
221 top.active = top.parent_active and !top.branch_taken and condition;
222 top.branch_taken = top.branch_taken or condition;
223 }
224 } else if (std.mem.eql(u8, direct.name, "else")) {
225 if (self.conditions.items.len > 0) {
226 const top = &self.conditions.items[self.conditions.items.len - 1];
227 top.active = top.parent_active and !top.branch_taken;
228 top.branch_taken = true;
229 }
230 } else if (std.mem.eql(u8, direct.name, "endif")) {
231 if (self.conditions.items.len > 0) _ = self.conditions.pop();
232 } else {
233 return false;
234 }
235
236 if (try rewriteHasEmbedDefined(self, out, direct)) return true;
237 if (try self.appendHasEmbedExpressions(out, line, source_dir)) return true;
238 try out.appendSlice(self.arena, line);
239 return true;
240 }
241
242 fn currentActive(self: *const Expander) bool {
243 if (self.conditions.items.len == 0) return true;
244 return self.conditions.items[self.conditions.items.len - 1].active;
245 }
246
247 fn appendHasEmbedExpressions(self: *Expander, out: *std.ArrayListUnmanaged(u8), line: []const u8, source_dir: []const u8) Error!bool {
248 var replaced = false;
249 const expanded = try self.expandHasEmbedExpressions(line, source_dir, &replaced);
250 if (!replaced) return false;
251 try out.appendSlice(self.arena, expanded);
252 self.changed = true;
253 return true;
254 }
255
256 fn expandHasEmbedExpressions(self: *Expander, line: []const u8, source_dir: []const u8, replaced: *bool) Error![]const u8 {
257 var index: usize = 0;
258 var out = std.ArrayListUnmanaged(u8).empty;
259 while (index < line.len) {
260 if (line[index] == '"') {
261 const end = findStringEnd(line, index) orelse line.len - 1;
262 try out.appendSlice(self.arena, line[index .. end + 1]);
263 index = end + 1;
264 continue;
265 }
266 if (line[index] == '\'') {
267 const end = findQuotedEnd(line, index, '\'') orelse line.len - 1;
268 try out.appendSlice(self.arena, line[index .. end + 1]);
269 index = end + 1;
270 continue;
271 }
272 if (!isIdentStart(line[index])) {
273 try out.append(self.arena, line[index]);
274 index += 1;
275 continue;
276 }
277 const start = index;
278 index += 1;
279 while (index < line.len and isIdentContinue(line[index])) : (index += 1) {}
280 const name = line[start..index];
281 if (!std.mem.eql(u8, name, "__has_embed")) {
282 try out.appendSlice(self.arena, name);
283 continue;
284 }
285 const call = skipHorizontal(line, index);
286 if (call >= line.len or line[call] != '(') {
287 try out.appendSlice(self.arena, name);
288 continue;
289 }
290 const end = findBalancedEnd(line, call) orelse {
291 try out.appendSlice(self.arena, line[start..]);
292 return out.toOwnedSlice(self.arena);
293 };
294 try appendPrint(self.arena, &out, "{d}", .{@backingInt(try self.hasEmbed(line[call + 1 .. end], source_dir))});
295 replaced.* = true;
296 index = end + 1;
297 }
298 return out.toOwnedSlice(self.arena);
299 }
300
301 fn hasEmbed(self: *Expander, raw: []const u8, source_dir: []const u8) Error!Status {
302 const expanded = try self.expandMacros(raw);
303 const request = parseRequest(expanded) orelse return .not_found;
304 const path = self.findResource(request, source_dir) orelse return .not_found;
305 const resource = sys.fs.readFileAlloc(self.arena, path, max_resource_bytes) catch return .not_found;
306 const count = if (request.params.limit) |limit| @min(limit, resource.len) else resource.len;
307 return if (count == 0) .empty else .found;
308 }
309
310 pub fn expandOutput(self: *Expander, preprocessed: []const u8) Error![]const u8 {
311 if (self.embeds.items.len == 0) return preprocessed;
312 var out = std.ArrayListUnmanaged(u8).empty;
313 var index: usize = 0;
314 while (index < preprocessed.len) {
315 if (std.mem.startsWith(u8, preprocessed[index..], sentinel_prefix)) {
316 var cursor = index + sentinel_prefix.len;
317 const digits_start = cursor;
318 while (cursor < preprocessed.len and std.ascii.isDigit(preprocessed[cursor])) : (cursor += 1) {}
319 if (cursor > digits_start and cursor + 2 <= preprocessed.len and std.mem.eql(u8, preprocessed[cursor .. cursor + 2], "__")) {
320 const id = std.fmt.parseInt(usize, preprocessed[digits_start..cursor], 10) catch null;
321 if (id) |embed_id| {
322 if (embed_id < self.embeds.items.len) {
323 const expanded = try self.expandEmbed(self.embeds.items[embed_id]);
324 try out.appendSlice(self.arena, expanded);
325 index = cursor + 2;
326 continue;
327 }
328 }
329 }
330 }
331 try out.append(self.arena, preprocessed[index]);
332 index += 1;
333 }
334 return out.toOwnedSlice(self.arena);
335 }
336
337 fn expandEmbed(self: *Expander, embed: Embed) Error![]const u8 {
338 const request = parseRequest(embed.raw) orelse return error.PreprocessFailed;
339 const path = self.findResource(request, embed.source_dir) orelse return error.PreprocessFailed;
340 const resource = sys.fs.readFileAlloc(self.arena, path, max_resource_bytes) catch |err| switch (err) {
341 error.OutOfMemory => return error.OutOfMemory,
342 else => return error.PreprocessFailed,
343 };
344 const count = if (request.params.limit) |limit| @min(limit, resource.len) else resource.len;
345 if (count == 0) return request.params.if_empty orelse "";
346
347 var out = std.ArrayListUnmanaged(u8).empty;
348 if (request.params.prefix) |prefix| try out.appendSlice(self.arena, prefix);
349 for (resource[0..count], 0..) |byte, byte_index| {
350 if (byte_index != 0) try out.append(self.arena, ',');
351 try appendPrint(self.arena, &out, "{d}", .{byte});
352 }
353 if (request.params.suffix) |suffix| try out.appendSlice(self.arena, suffix);
354 return out.toOwnedSlice(self.arena);
355 }
356
357 fn findResource(self: *Expander, request: Request, source_dir: []const u8) ?[]const u8 {
358 if (std.fs.path.isAbsolute(request.name) and fileExists(request.name)) return request.name;
359 if (request.quoted) {
360 if (self.findResourceInDir(source_dir, request.name)) |path| return path;
361 if (self.findResourceInDir(".", request.name)) |path| return path;
362 }
363 for (self.options.include_dirs) |dir| {
364 if (self.findResourceInDir(dir, request.name)) |path| return path;
365 }
366 if (!request.quoted) {
367 if (self.findResourceInDir(source_dir, request.name)) |path| return path;
368 if (self.findResourceInDir(".", request.name)) |path| return path;
369 }
370 return null;
371 }
372
373 fn findInclude(self: *Expander, include: Include, source_dir: []const u8) ?[]const u8 {
374 if (std.fs.path.isAbsolute(include.name) and fileExists(include.name)) return include.name;
375 if (include.quoted) {
376 if (self.findResourceInDir(source_dir, include.name)) |path| return path;
377 if (self.findResourceInDir(".", include.name)) |path| return path;
378 }
379 for (self.options.include_dirs) |dir| {
380 if (self.findResourceInDir(dir, include.name)) |path| return path;
381 }
382 if (!include.quoted) {
383 if (self.findResourceInDir(source_dir, include.name)) |path| return path;
384 if (self.findResourceInDir(".", include.name)) |path| return path;
385 }
386 return null;
387 }
388
389 fn findResourceInDir(self: *Expander, dir: []const u8, name: []const u8) ?[]const u8 {
390 const path = std.fs.path.join(self.arena, &.{ dir, name }) catch return null;
391 if (fileExists(path)) return path;
392 return null;
393 }
394
395 fn recordDefine(self: *Expander, raw: []const u8) Error!void {
396 const macro = (try parseDefine(self.arena, raw)) orelse return;
397 try self.macros.append(self.arena, macro);
398 }
399
400 fn recordUndef(self: *Expander, raw: []const u8) void {
401 const name = parseUndef(raw) orelse return;
402 var index: usize = 0;
403 while (index < self.macros.items.len) {
404 if (std.mem.eql(u8, self.macros.items[index].name, name)) {
405 _ = self.macros.orderedRemove(index);
406 continue;
407 }
408 index += 1;
409 }
410 }
411
412 fn expandMacros(self: *Expander, raw: []const u8) Error![]const u8 {
413 var current = raw;
414 var pass: usize = 0;
415 while (pass < 16) : (pass += 1) {
416 var changed = false;
417 const expanded = try self.expandMacrosOnce(current, &changed);
418 if (!changed) return current;
419 current = expanded;
420 }
421 return current;
422 }
423
424 fn expandMacrosOnce(self: *Expander, raw: []const u8, changed: *bool) Error![]const u8 {
425 var out = std.ArrayListUnmanaged(u8).empty;
426 var index: usize = 0;
427 while (index < raw.len) {
428 if (raw[index] == '"') {
429 const end = findStringEnd(raw, index) orelse raw.len - 1;
430 try out.appendSlice(self.arena, raw[index .. end + 1]);
431 index = end + 1;
432 continue;
433 }
434 if (raw[index] == '\'') {
435 const end = findQuotedEnd(raw, index, '\'') orelse raw.len - 1;
436 try out.appendSlice(self.arena, raw[index .. end + 1]);
437 index = end + 1;
438 continue;
439 }
440 if (isIdentStart(raw[index])) {
441 const start = index;
442 index += 1;
443 while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {}
444 const name = raw[start..index];
445 if (self.lookupMacro(name)) |macro| {
446 if (!macro.function_like) {
447 try out.appendSlice(self.arena, macro.replacement);
448 changed.* = true;
449 } else {
450 const call = skipHorizontal(raw, index);
451 if (call >= raw.len or raw[call] != '(') {
452 try out.appendSlice(self.arena, name);
453 } else if (findBalancedEnd(raw, call)) |end| {
454 if (try self.expandFunctionMacro(macro, raw[call + 1 .. end])) |expanded| {
455 try out.appendSlice(self.arena, expanded);
456 changed.* = true;
457 index = end + 1;
458 } else {
459 try out.appendSlice(self.arena, raw[start .. end + 1]);
460 index = end + 1;
461 }
462 } else {
463 try out.appendSlice(self.arena, name);
464 }
465 }
466 } else {
467 try out.appendSlice(self.arena, name);
468 }
469 continue;
470 }
471 try out.append(self.arena, raw[index]);
472 index += 1;
473 }
474 return out.toOwnedSlice(self.arena);
475 }
476
477 fn expandFunctionMacro(self: *Expander, macro: Macro, raw_arguments: []const u8) Error!?[]const u8 {
478 const arguments = try self.parseMacroArguments(macro, raw_arguments) orelse return null;
479 var expanded = try self.arena.alloc([]const u8, arguments.len);
480 for (arguments, 0..) |argument, argument_index| {
481 expanded[argument_index] = try self.expandMacros(argument);
482 }
483 return try self.substituteFunctionMacro(macro, expanded);
484 }
485
486 fn parseMacroArguments(self: *Expander, macro: Macro, raw: []const u8) Error!?[]const []const u8 {
487 var pieces = std.ArrayListUnmanaged([]const u8).empty;
488 const trimmed = std.mem.trim(u8, raw, " \t\r");
489 if (trimmed.len == 0) {
490 if (macro.params.len == 0) return &.{};
491 try pieces.append(self.arena, "");
492 } else {
493 var start: usize = 0;
494 var index: usize = 0;
495 var depth: usize = 0;
496 while (index < raw.len) : (index += 1) {
497 switch (raw[index]) {
498 '"' => index = findStringEnd(raw, index) orelse return null,
499 '\'' => index = findQuotedEnd(raw, index, '\'') orelse return null,
500 '(' => depth += 1,
501 ')' => {
502 if (depth > 0) depth -= 1;
503 },
504 ',' => if (depth == 0) {
505 try pieces.append(self.arena, std.mem.trim(u8, raw[start..index], " \t\r"));
506 start = index + 1;
507 },
508 else => {},
509 }
510 }
511 try pieces.append(self.arena, std.mem.trim(u8, raw[start..], " \t\r"));
512 }
513
514 if (!macro.variadic) {
515 if (pieces.items.len != macro.params.len) return null;
516 return try pieces.toOwnedSlice(self.arena);
517 }
518
519 const fixed_count = macro.params.len - 1;
520 if (pieces.items.len < fixed_count) return null;
521 var arguments = try self.arena.alloc([]const u8, macro.params.len);
522 for (arguments[0..fixed_count], 0..) |*argument, argument_index| argument.* = pieces.items[argument_index];
523 if (pieces.items.len == fixed_count) {
524 arguments[fixed_count] = "";
525 } else if (pieces.items.len == fixed_count + 1) {
526 arguments[fixed_count] = pieces.items[fixed_count];
527 } else {
528 var joined = std.ArrayListUnmanaged(u8).empty;
529 for (pieces.items[fixed_count..], 0..) |piece, piece_index| {
530 if (piece_index != 0) try joined.append(self.arena, ',');
531 try joined.appendSlice(self.arena, piece);
532 }
533 arguments[fixed_count] = try joined.toOwnedSlice(self.arena);
534 }
535 return arguments;
536 }
537
538 fn substituteFunctionMacro(self: *Expander, macro: Macro, arguments: []const []const u8) Error![]const u8 {
539 var out = std.ArrayListUnmanaged(u8).empty;
540 var index: usize = 0;
541 while (index < macro.replacement.len) {
542 if (macro.replacement[index] == '"') {
543 const end = findStringEnd(macro.replacement, index) orelse macro.replacement.len - 1;
544 try out.appendSlice(self.arena, macro.replacement[index .. end + 1]);
545 index = end + 1;
546 continue;
547 }
548 if (macro.replacement[index] == '\'') {
549 const end = findQuotedEnd(macro.replacement, index, '\'') orelse macro.replacement.len - 1;
550 try out.appendSlice(self.arena, macro.replacement[index .. end + 1]);
551 index = end + 1;
552 continue;
553 }
554 if (!isIdentStart(macro.replacement[index])) {
555 try out.append(self.arena, macro.replacement[index]);
556 index += 1;
557 continue;
558 }
559 const start = index;
560 index += 1;
561 while (index < macro.replacement.len and isIdentContinue(macro.replacement[index])) : (index += 1) {}
562 const name = macro.replacement[start..index];
563 if (macro.variadic and std.mem.eql(u8, name, "__VA_OPT__")) {
564 const call = skipHorizontal(macro.replacement, index);
565 if (call < macro.replacement.len and macro.replacement[call] == '(') {
566 if (findBalancedEnd(macro.replacement, call)) |end| {
567 if (std.mem.trim(u8, arguments[arguments.len - 1], " \t\r").len != 0) {
568 try out.appendSlice(self.arena, macro.replacement[call + 1 .. end]);
569 }
570 index = end + 1;
571 continue;
572 }
573 }
574 }
575 if (paramIndex(macro, name)) |argument_index| {
576 try out.appendSlice(self.arena, arguments[argument_index]);
577 } else {
578 try out.appendSlice(self.arena, name);
579 }
580 }
581 return out.toOwnedSlice(self.arena);
582 }
583
584 fn lookupMacro(self: *const Expander, name: []const u8) ?Macro {
585 var index = self.macros.items.len;
586 while (index > 0) {
587 index -= 1;
588 const macro = self.macros.items[index];
589 if (std.mem.eql(u8, macro.name, name)) return macro;
590 }
591 if (builtinMacro(name)) |replacement| return .{ .name = name, .replacement = replacement };
592 return null;
593 }
594
595 fn isMacroDefined(self: *const Expander, name: []const u8) bool {
596 if (std.mem.eql(u8, name, "__has_embed")) return true;
597 return self.lookupMacro(name) != null;
598 }
599
600 fn evalCondition(self: *Expander, raw: []const u8, source_dir: []const u8) Error!bool {
601 var embed_replaced = false;
602 const with_embed = try self.expandHasEmbedExpressions(raw, source_dir, &embed_replaced);
603 const with_defined = try self.replaceDefinedOperators(with_embed);
604 const expanded = try self.expandMacros(with_defined);
605 var parser = ConditionParser{ .text = expanded };
606 const value = parser.parse() orelse return true;
607 return value != 0;
608 }
609
610 fn replaceDefinedOperators(self: *Expander, raw: []const u8) Error![]const u8 {
611 var out = std.ArrayListUnmanaged(u8).empty;
612 var index: usize = 0;
613 while (index < raw.len) {
614 if (raw[index] == '"') {
615 const end = findStringEnd(raw, index) orelse raw.len - 1;
616 try out.appendSlice(self.arena, raw[index .. end + 1]);
617 index = end + 1;
618 continue;
619 }
620 if (raw[index] == '\'') {
621 const end = findQuotedEnd(raw, index, '\'') orelse raw.len - 1;
622 try out.appendSlice(self.arena, raw[index .. end + 1]);
623 index = end + 1;
624 continue;
625 }
626 if (!isIdentStart(raw[index])) {
627 try out.append(self.arena, raw[index]);
628 index += 1;
629 continue;
630 }
631 const start = index;
632 index += 1;
633 while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {}
634 const name = raw[start..index];
635 if (!std.mem.eql(u8, name, "defined")) {
636 try out.appendSlice(self.arena, name);
637 continue;
638 }
639
640 var cursor = skipHorizontal(raw, index);
641 var parenthesized = false;
642 if (cursor < raw.len and raw[cursor] == '(') {
643 parenthesized = true;
644 cursor = skipHorizontal(raw, cursor + 1);
645 }
646 if (cursor >= raw.len or !isIdentStart(raw[cursor])) {
647 try out.appendSlice(self.arena, name);
648 continue;
649 }
650 const macro_start = cursor;
651 cursor += 1;
652 while (cursor < raw.len and isIdentContinue(raw[cursor])) : (cursor += 1) {}
653 const macro_name = raw[macro_start..cursor];
654 cursor = skipHorizontal(raw, cursor);
655 if (parenthesized) {
656 if (cursor >= raw.len or raw[cursor] != ')') {
657 try out.appendSlice(self.arena, name);
658 continue;
659 }
660 cursor += 1;
661 }
662 try out.append(self.arena, if (self.isMacroDefined(macro_name)) '1' else '0');
663 index = cursor;
664 }
665 return out.toOwnedSlice(self.arena);
666 }
667 };
668
669 const ConditionParser = struct {
670 text: []const u8,
671 index: usize = 0,
672
673 fn parse(self: *ConditionParser) ?i128 {
674 const value = self.parseOr() orelse return null;
675 self.skip();
676 if (self.index != self.text.len) return null;
677 return value;
678 }
679
680 fn parseOr(self: *ConditionParser) ?i128 {
681 var value = self.parseAnd() orelse return null;
682 while (self.consume("||")) {
683 const right = self.parseAnd() orelse return null;
684 value = if (value != 0 or right != 0) 1 else 0;
685 }
686 return value;
687 }
688
689 fn parseAnd(self: *ConditionParser) ?i128 {
690 var value = self.parseBitOr() orelse return null;
691 while (self.consume("&&")) {
692 const right = self.parseBitOr() orelse return null;
693 value = if (value != 0 and right != 0) 1 else 0;
694 }
695 return value;
696 }
697
698 fn parseBitOr(self: *ConditionParser) ?i128 {
699 var value = self.parseBitXor() orelse return null;
700 while (self.consumeSingle('|', "||")) {
701 const right = self.parseBitXor() orelse return null;
702 value |= right;
703 }
704 return value;
705 }
706
707 fn parseBitXor(self: *ConditionParser) ?i128 {
708 var value = self.parseBitAnd() orelse return null;
709 while (self.consume("^")) {
710 const right = self.parseBitAnd() orelse return null;
711 value ^= right;
712 }
713 return value;
714 }
715
716 fn parseBitAnd(self: *ConditionParser) ?i128 {
717 var value = self.parseEquality() orelse return null;
718 while (self.consumeSingle('&', "&&")) {
719 const right = self.parseEquality() orelse return null;
720 value &= right;
721 }
722 return value;
723 }
724
725 fn parseEquality(self: *ConditionParser) ?i128 {
726 var value = self.parseRelational() orelse return null;
727 while (true) {
728 if (self.consume("==")) {
729 const right = self.parseRelational() orelse return null;
730 value = if (value == right) 1 else 0;
731 } else if (self.consume("!=")) {
732 const right = self.parseRelational() orelse return null;
733 value = if (value != right) 1 else 0;
734 } else {
735 return value;
736 }
737 }
738 }
739
740 fn parseRelational(self: *ConditionParser) ?i128 {
741 var value = self.parseShift() orelse return null;
742 while (true) {
743 if (self.consume("<=")) {
744 const right = self.parseShift() orelse return null;
745 value = if (value <= right) 1 else 0;
746 } else if (self.consume(">=")) {
747 const right = self.parseShift() orelse return null;
748 value = if (value >= right) 1 else 0;
749 } else if (self.consume("<")) {
750 const right = self.parseShift() orelse return null;
751 value = if (value < right) 1 else 0;
752 } else if (self.consume(">")) {
753 const right = self.parseShift() orelse return null;
754 value = if (value > right) 1 else 0;
755 } else {
756 return value;
757 }
758 }
759 }
760
761 fn parseShift(self: *ConditionParser) ?i128 {
762 var value = self.parseAdd() orelse return null;
763 while (true) {
764 if (self.consume("<<")) {
765 const right = self.parseAdd() orelse return null;
766 if (right < 0 or right > 127) return null;
767 value = value << @intCast(right);
768 } else if (self.consume(">>")) {
769 const right = self.parseAdd() orelse return null;
770 if (right < 0 or right > 127) return null;
771 value = value >> @intCast(right);
772 } else {
773 return value;
774 }
775 }
776 }
777
778 fn parseAdd(self: *ConditionParser) ?i128 {
779 var value = self.parseMul() orelse return null;
780 while (true) {
781 if (self.consume("+")) {
782 const right = self.parseMul() orelse return null;
783 value += right;
784 } else if (self.consume("-")) {
785 const right = self.parseMul() orelse return null;
786 value -= right;
787 } else {
788 return value;
789 }
790 }
791 }
792
793 fn parseMul(self: *ConditionParser) ?i128 {
794 var value = self.parseUnary() orelse return null;
795 while (true) {
796 if (self.consume("*")) {
797 const right = self.parseUnary() orelse return null;
798 value *= right;
799 } else if (self.consume("/")) {
800 const right = self.parseUnary() orelse return null;
801 if (right == 0) return null;
802 value = @divTrunc(value, right);
803 } else if (self.consume("%")) {
804 const right = self.parseUnary() orelse return null;
805 if (right == 0) return null;
806 value = @rem(value, right);
807 } else {
808 return value;
809 }
810 }
811 }
812
813 fn parseUnary(self: *ConditionParser) ?i128 {
814 if (self.consume("!")) return if ((self.parseUnary() orelse return null) == 0) 1 else 0;
815 if (self.consume("+")) return self.parseUnary();
816 if (self.consume("-")) return -(self.parseUnary() orelse return null);
817 if (self.consume("~")) return ~(self.parseUnary() orelse return null);
818 return self.parsePrimary();
819 }
820
821 fn parsePrimary(self: *ConditionParser) ?i128 {
822 self.skip();
823 if (self.index >= self.text.len) return null;
824 if (self.text[self.index] == '(') {
825 self.index += 1;
826 const value = self.parseOr() orelse return null;
827 self.skip();
828 if (self.index >= self.text.len or self.text[self.index] != ')') return null;
829 self.index += 1;
830 return value;
831 }
832 if (self.text[self.index] == '\'') return self.parseChar();
833 if (std.ascii.isDigit(self.text[self.index])) return self.parseNumber();
834 if (isIdentStart(self.text[self.index])) {
835 self.index += 1;
836 while (self.index < self.text.len and isIdentContinue(self.text[self.index])) : (self.index += 1) {}
837 self.skip();
838 if (self.index < self.text.len and self.text[self.index] == '(') return null;
839 return 0;
840 }
841 return null;
842 }
843
844 fn parseNumber(self: *ConditionParser) ?i128 {
845 const start = self.index;
846 var base: u8 = 10;
847 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')) {
848 base = 16;
849 self.index += 2;
850 }
851 const digits_start = self.index;
852 while (self.index < self.text.len) : (self.index += 1) {
853 if (base == 16) {
854 if (!std.ascii.isHex(self.text[self.index])) break;
855 } else if (!std.ascii.isDigit(self.text[self.index])) {
856 break;
857 }
858 }
859 if (self.index == digits_start) return null;
860 const digits = self.text[digits_start..self.index];
861 const value = std.fmt.parseInt(i128, digits, base) catch return null;
862 while (self.index < self.text.len and (isIdentContinue(self.text[self.index]) or self.text[self.index] == '\'')) : (self.index += 1) {}
863 _ = start;
864 return value;
865 }
866
867 fn parseChar(self: *ConditionParser) ?i128 {
868 self.index += 1;
869 if (self.index >= self.text.len) return null;
870 const value: i128 = if (self.text[self.index] == '\\') value: {
871 self.index += 1;
872 if (self.index >= self.text.len) return null;
873 break :value switch (self.text[self.index]) {
874 'n' => 10,
875 'r' => 13,
876 't' => 9,
877 '0' => 0,
878 else => self.text[self.index],
879 };
880 } else self.text[self.index];
881 self.index += 1;
882 if (self.index >= self.text.len or self.text[self.index] != '\'') return null;
883 self.index += 1;
884 return value;
885 }
886
887 fn consume(self: *ConditionParser, token: []const u8) bool {
888 self.skip();
889 if (!std.mem.startsWith(u8, self.text[self.index..], token)) return false;
890 self.index += token.len;
891 return true;
892 }
893
894 fn consumeSingle(self: *ConditionParser, token: u8, excluded: []const u8) bool {
895 self.skip();
896 if (std.mem.startsWith(u8, self.text[self.index..], excluded)) return false;
897 if (self.index >= self.text.len or self.text[self.index] != token) return false;
898 self.index += 1;
899 return true;
900 }
901
902 fn skip(self: *ConditionParser) void {
903 self.index = skipHorizontal(self.text, self.index);
904 }
905 };
906
907 fn rewriteHasEmbedDefined(expander: *Expander, out: *std.ArrayListUnmanaged(u8), direct: Directive) Error!bool {
908 const positive = std.mem.eql(u8, direct.name, "ifdef") or std.mem.eql(u8, direct.name, "elifdef");
909 const negative = std.mem.eql(u8, direct.name, "ifndef") or std.mem.eql(u8, direct.name, "elifndef");
910 if (!positive and !negative) return false;
911 if (!std.mem.eql(u8, std.mem.trim(u8, direct.argument, " \t\r"), "__has_embed")) return false;
912 const elif = std.mem.startsWith(u8, direct.name, "elif");
913 try out.appendSlice(expander.arena, if (elif) "#elif " else "#if ");
914 try out.append(expander.arena, if (positive) '1' else '0');
915 expander.changed = true;
916 return true;
917 }
918
919 fn parseRequest(raw: []const u8) ?Request {
920 var index = skipHorizontal(raw, 0);
921 if (index >= raw.len) return null;
922 const quoted = switch (raw[index]) {
923 '"' => true,
924 '<' => false,
925 else => return null,
926 };
927 const end = if (quoted)
928 findStringEnd(raw, index) orelse return null
929 else
930 std.mem.indexOfScalarPos(u8, raw, index + 1, '>') orelse return null;
931 const name = raw[index + 1 .. end];
932 index = skipHorizontal(raw, end + 1);
933 const params = parseParams(raw[index..]) orelse return null;
934 return .{ .name = name, .quoted = quoted, .params = params };
935 }
936
937 fn parseInclude(raw: []const u8) ?Include {
938 var index = skipHorizontal(raw, 0);
939 if (index >= raw.len) return null;
940 const quoted = switch (raw[index]) {
941 '"' => true,
942 '<' => false,
943 else => return null,
944 };
945 const end = if (quoted)
946 findStringEnd(raw, index) orelse return null
947 else
948 std.mem.indexOfScalarPos(u8, raw, index + 1, '>') orelse return null;
949 const name = raw[index + 1 .. end];
950 index = skipHorizontal(raw, end + 1);
951 if (index != raw.len) return null;
952 return .{ .name = name, .quoted = quoted };
953 }
954
955 fn parseDefine(arena: std.mem.Allocator, raw: []const u8) Error!?Macro {
956 var index = skipHorizontal(raw, 0);
957 if (index >= raw.len or !isIdentStart(raw[index])) return null;
958 const start = index;
959 index += 1;
960 while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {}
961 const name = raw[start..index];
962 if (index < raw.len and raw[index] == '(') {
963 index += 1;
964 var params = std.ArrayListUnmanaged([]const u8).empty;
965 var variadic = false;
966 index = skipHorizontal(raw, index);
967 if (index < raw.len and raw[index] == ')') {
968 index += 1;
969 } else {
970 while (index < raw.len) {
971 index = skipHorizontal(raw, index);
972 if (index >= raw.len) return null;
973 if (index + 3 <= raw.len and std.mem.eql(u8, raw[index .. index + 3], "...")) {
974 try params.append(arena, "__VA_ARGS__");
975 variadic = true;
976 index += 3;
977 index = skipHorizontal(raw, index);
978 if (index >= raw.len or raw[index] != ')') return null;
979 index += 1;
980 break;
981 }
982 if (!isIdentStart(raw[index])) return null;
983 const param_start = index;
984 index += 1;
985 while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {}
986 const param = raw[param_start..index];
987 for (params.items) |existing| {
988 if (std.mem.eql(u8, existing, param)) return null;
989 }
990 try params.append(arena, param);
991 index = skipHorizontal(raw, index);
992 if (index >= raw.len) return null;
993 if (raw[index] == ',') {
994 index += 1;
995 continue;
996 }
997 if (raw[index] == ')') {
998 index += 1;
999 break;
1000 }
1001 return null;
1002 }
1003 }
1004 const replacement = std.mem.trim(u8, raw[index..], " \t\r");
1005 return .{
1006 .name = name,
1007 .replacement = replacement,
1008 .params = try params.toOwnedSlice(arena),
1009 .function_like = true,
1010 .variadic = variadic,
1011 };
1012 }
1013 const replacement = std.mem.trim(u8, raw[index..], " \t\r");
1014 return .{ .name = name, .replacement = replacement };
1015 }
1016
1017 fn paramIndex(macro: Macro, name: []const u8) ?usize {
1018 for (macro.params, 0..) |param, index| {
1019 if (std.mem.eql(u8, param, name)) return index;
1020 }
1021 return null;
1022 }
1023
1024 fn builtinMacro(name: []const u8) ?[]const u8 {
1025 if (std.mem.eql(u8, name, "__STDC_EMBED_NOT_FOUND__")) return "0";
1026 if (std.mem.eql(u8, name, "__STDC_EMBED_FOUND__")) return "1";
1027 if (std.mem.eql(u8, name, "__STDC_EMBED_EMPTY__")) return "2";
1028 return null;
1029 }
1030
1031 fn parseUndef(raw: []const u8) ?[]const u8 {
1032 var index = skipHorizontal(raw, 0);
1033 if (index >= raw.len or !isIdentStart(raw[index])) return null;
1034 const start = index;
1035 index += 1;
1036 while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {}
1037 const name = raw[start..index];
1038 index = skipHorizontal(raw, index);
1039 if (index != raw.len) return null;
1040 return name;
1041 }
1042
1043 fn parseParams(raw: []const u8) ?Params {
1044 var params: Params = .{};
1045 var index: usize = 0;
1046 while (true) {
1047 index = skipHorizontal(raw, index);
1048 if (index >= raw.len) return params;
1049 const name_start = index;
1050 if (!isIdentStart(raw[index])) return null;
1051 index += 1;
1052 while (index < raw.len and isIdentContinue(raw[index])) : (index += 1) {}
1053 const name = raw[name_start..index];
1054 index = skipHorizontal(raw, index);
1055 if (index >= raw.len or raw[index] != '(') return null;
1056 const end = findBalancedEnd(raw, index) orelse return null;
1057 const body = std.mem.trim(u8, raw[index + 1 .. end], " \t\r");
1058 if (std.mem.eql(u8, name, "limit")) {
1059 if (params.limit != null) return null;
1060 params.limit = parseLimit(body) orelse return null;
1061 } else if (std.mem.eql(u8, name, "prefix")) {
1062 if (params.prefix != null) return null;
1063 params.prefix = body;
1064 } else if (std.mem.eql(u8, name, "suffix")) {
1065 if (params.suffix != null) return null;
1066 params.suffix = body;
1067 } else if (std.mem.eql(u8, name, "if_empty")) {
1068 if (params.if_empty != null) return null;
1069 params.if_empty = body;
1070 } else {
1071 return null;
1072 }
1073 index = end + 1;
1074 }
1075 }
1076
1077 fn parseLimit(raw: []const u8) ?usize {
1078 if (raw.len == 0) return null;
1079 if (std.mem.startsWith(u8, raw, "0x") or std.mem.startsWith(u8, raw, "0X")) return std.fmt.parseInt(usize, raw[2..], 16) catch null;
1080 return std.fmt.parseInt(usize, raw, 10) catch null;
1081 }
1082
1083 fn parseDirective(line: []const u8) ?Directive {
1084 var index = skipHorizontal(line, 0);
1085 if (index >= line.len or line[index] != '#') return null;
1086 index = skipHorizontal(line, index + 1);
1087 if (index >= line.len or !isIdentStart(line[index])) return null;
1088 const start = index;
1089 index += 1;
1090 while (index < line.len and isIdentContinue(line[index])) : (index += 1) {}
1091 return .{ .name = line[start..index], .argument = line[index..] };
1092 }
1093
1094 fn findStringEnd(raw: []const u8, start: usize) ?usize {
1095 return findQuotedEnd(raw, start, '"');
1096 }
1097
1098 fn findQuotedEnd(raw: []const u8, start: usize, quote: u8) ?usize {
1099 var index = start + 1;
1100 while (index < raw.len) : (index += 1) {
1101 if (raw[index] == '\\') {
1102 index += 1;
1103 continue;
1104 }
1105 if (raw[index] == quote) return index;
1106 }
1107 return null;
1108 }
1109
1110 fn findBalancedEnd(raw: []const u8, start: usize) ?usize {
1111 var depth: usize = 0;
1112 var index = start;
1113 while (index < raw.len) : (index += 1) {
1114 switch (raw[index]) {
1115 '"' => index = findStringEnd(raw, index) orelse return null,
1116 '(' => depth += 1,
1117 ')' => {
1118 depth -= 1;
1119 if (depth == 0) return index;
1120 },
1121 else => {},
1122 }
1123 }
1124 return null;
1125 }
1126
1127 fn appendLineMarker(arena: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), path: []const u8) Error!void {
1128 try out.appendSlice(arena, "#line 1 \"");
1129 for (path) |byte| {
1130 if (byte == '"' or byte == '\\') try out.append(arena, '\\');
1131 try out.append(arena, byte);
1132 }
1133 try out.appendSlice(arena, "\"\n");
1134 }
1135
1136 fn appendPrint(
1137 arena: std.mem.Allocator,
1138 out: *std.ArrayListUnmanaged(u8),
1139 comptime fmt: []const u8,
1140 args: anytype,
1141 ) Error!void {
1142 const text = std.fmt.allocPrint(arena, fmt, args) catch return error.OutOfMemory;
1143 try out.appendSlice(arena, text);
1144 }
1145
1146 fn sourceDirectory(arena: std.mem.Allocator, source_path: []const u8) ![]const u8 {
1147 const dir = std.fs.path.dirname(source_path) orelse ".";
1148 return std.fs.path.resolve(arena, &.{dir});
1149 }
1150
1151 fn fileExists(path: []const u8) bool {
1152 sys.fs.cwd().access(sys.fs.debugIo(), path, .{}) catch return false;
1153 return true;
1154 }
1155
1156 fn skipHorizontal(text: []const u8, start: usize) usize {
1157 var index = start;
1158 while (index < text.len and isHorizontal(text[index])) : (index += 1) {}
1159 return index;
1160 }
1161
1162 fn isHorizontal(byte: u8) bool {
1163 return byte == ' ' or byte == '\t' or byte == '\r';
1164 }
1165
1166 fn isIdentStart(byte: u8) bool {
1167 return std.ascii.isAlphabetic(byte) or byte == '_';
1168 }
1169
1170 fn isIdentContinue(byte: u8) bool {
1171 return isIdentStart(byte) or std.ascii.isDigit(byte);
1172 }