master
1const std = @import("std");
2const mem = std.mem;
3const assert = std.debug.assert;
4
5const aro = @import("aro");
6const CToken = aro.Tokenizer.Token;
7
8const helpers = @import("helpers.zig");
9const Translator = @import("Translator.zig");
10const Error = Translator.Error;
11pub const MacroProcessingError = Error || error{UnexpectedMacroToken};
12
13const Impl = std.meta.DeclEnum(std.zig.c_translation.helpers);
14const Template = struct { []const u8, Impl };
15
16/// Templates must be function-like macros
17/// first element is macro source, second element is the name of the function
18/// in __helpers which implements it
19const templates = [_]Template{
20 .{ "f_SUFFIX(X) (X ## f)", .F_SUFFIX },
21 .{ "F_SUFFIX(X) (X ## F)", .F_SUFFIX },
22
23 .{ "u_SUFFIX(X) (X ## u)", .U_SUFFIX },
24 .{ "U_SUFFIX(X) (X ## U)", .U_SUFFIX },
25
26 .{ "l_SUFFIX(X) (X ## l)", .L_SUFFIX },
27 .{ "L_SUFFIX(X) (X ## L)", .L_SUFFIX },
28
29 .{ "ul_SUFFIX(X) (X ## ul)", .UL_SUFFIX },
30 .{ "uL_SUFFIX(X) (X ## uL)", .UL_SUFFIX },
31 .{ "Ul_SUFFIX(X) (X ## Ul)", .UL_SUFFIX },
32 .{ "UL_SUFFIX(X) (X ## UL)", .UL_SUFFIX },
33
34 .{ "ll_SUFFIX(X) (X ## ll)", .LL_SUFFIX },
35 .{ "LL_SUFFIX(X) (X ## LL)", .LL_SUFFIX },
36
37 .{ "ull_SUFFIX(X) (X ## ull)", .ULL_SUFFIX },
38 .{ "uLL_SUFFIX(X) (X ## uLL)", .ULL_SUFFIX },
39 .{ "Ull_SUFFIX(X) (X ## Ull)", .ULL_SUFFIX },
40 .{ "ULL_SUFFIX(X) (X ## ULL)", .ULL_SUFFIX },
41
42 .{ "f_SUFFIX(X) X ## f", .F_SUFFIX },
43 .{ "F_SUFFIX(X) X ## F", .F_SUFFIX },
44
45 .{ "u_SUFFIX(X) X ## u", .U_SUFFIX },
46 .{ "U_SUFFIX(X) X ## U", .U_SUFFIX },
47
48 .{ "l_SUFFIX(X) X ## l", .L_SUFFIX },
49 .{ "L_SUFFIX(X) X ## L", .L_SUFFIX },
50
51 .{ "ul_SUFFIX(X) X ## ul", .UL_SUFFIX },
52 .{ "uL_SUFFIX(X) X ## uL", .UL_SUFFIX },
53 .{ "Ul_SUFFIX(X) X ## Ul", .UL_SUFFIX },
54 .{ "UL_SUFFIX(X) X ## UL", .UL_SUFFIX },
55
56 .{ "ll_SUFFIX(X) X ## ll", .LL_SUFFIX },
57 .{ "LL_SUFFIX(X) X ## LL", .LL_SUFFIX },
58
59 .{ "ull_SUFFIX(X) X ## ull", .ULL_SUFFIX },
60 .{ "uLL_SUFFIX(X) X ## uLL", .ULL_SUFFIX },
61 .{ "Ull_SUFFIX(X) X ## Ull", .ULL_SUFFIX },
62 .{ "ULL_SUFFIX(X) X ## ULL", .ULL_SUFFIX },
63
64 .{ "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL },
65 .{ "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL },
66
67 .{
68 \\wl_container_of(ptr, sample, member) \
69 \\(__typeof__(sample))((char *)(ptr) - \
70 \\ offsetof(__typeof__(*sample), member))
71 ,
72 .WL_CONTAINER_OF,
73 },
74
75 .{ "IGNORE_ME(X) ((void)(X))", .DISCARD },
76 .{ "IGNORE_ME(X) (void)(X)", .DISCARD },
77 .{ "IGNORE_ME(X) ((const void)(X))", .DISCARD },
78 .{ "IGNORE_ME(X) (const void)(X)", .DISCARD },
79 .{ "IGNORE_ME(X) ((volatile void)(X))", .DISCARD },
80 .{ "IGNORE_ME(X) (volatile void)(X)", .DISCARD },
81 .{ "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD },
82 .{ "IGNORE_ME(X) (const volatile void)(X)", .DISCARD },
83 .{ "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD },
84 .{ "IGNORE_ME(X) (volatile const void)(X)", .DISCARD },
85};
86
87const Pattern = struct {
88 slicer: MacroSlicer,
89 impl: Impl,
90
91 fn init(pl: *Pattern, allocator: mem.Allocator, template: Template) Error!void {
92 const source = template[0];
93 const impl = template[1];
94 var tok_list: std.ArrayList(CToken) = .empty;
95 defer tok_list.deinit(allocator);
96
97 pl.* = .{
98 .slicer = try tokenizeMacro(allocator, source, &tok_list),
99 .impl = impl,
100 };
101 }
102
103 fn deinit(pl: *Pattern, allocator: mem.Allocator) void {
104 allocator.free(pl.slicer.tokens);
105 pl.* = undefined;
106 }
107
108 /// This function assumes that `ms` has already been validated to contain a function-like
109 /// macro, and that the parsed template macro in `pl` also contains a function-like
110 /// macro. Please review this logic carefully if changing that assumption. Two
111 /// function-like macros are considered equivalent if and only if they contain the same
112 /// list of tokens, modulo parameter names.
113 fn matches(pat: Pattern, ms: MacroSlicer) bool {
114 if (ms.params != pat.slicer.params) return false;
115 if (ms.tokens.len != pat.slicer.tokens.len) return false;
116
117 for (ms.tokens, pat.slicer.tokens) |macro_tok, pat_tok| {
118 if (macro_tok.id != pat_tok.id) return false;
119 switch (macro_tok.id) {
120 .macro_param, .macro_param_no_expand => {
121 // `.end` is the parameter index.
122 if (macro_tok.end != pat_tok.end) return false;
123 },
124 .identifier, .extended_identifier, .string_literal, .char_literal, .pp_num => {
125 const macro_bytes = ms.slice(macro_tok);
126 const pattern_bytes = pat.slicer.slice(pat_tok);
127
128 if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false;
129 },
130 else => {
131 // other tags correspond to keywords and operators that do not contain a "payload"
132 // that can vary
133 },
134 }
135 }
136 return true;
137 }
138};
139
140const PatternList = @This();
141
142patterns: []Pattern,
143
144pub const MacroSlicer = struct {
145 source: []const u8,
146 tokens: []const CToken,
147 params: u32,
148
149 fn slice(pl: MacroSlicer, token: CToken) []const u8 {
150 return pl.source[token.start..token.end];
151 }
152};
153
154pub fn init(allocator: mem.Allocator) Error!PatternList {
155 const patterns = try allocator.alloc(Pattern, templates.len);
156 for (patterns, templates) |*pattern, template| {
157 try pattern.init(allocator, template);
158 }
159 return .{ .patterns = patterns };
160}
161
162pub fn deinit(pl: *PatternList, allocator: mem.Allocator) void {
163 for (pl.patterns) |*pattern| pattern.deinit(allocator);
164 allocator.free(pl.patterns);
165 pl.* = undefined;
166}
167
168pub fn match(pl: PatternList, ms: MacroSlicer) Error!?Impl {
169 for (pl.patterns) |pattern| if (pattern.matches(ms)) return pattern.impl;
170 return null;
171}
172
173fn tokenizeMacro(allocator: mem.Allocator, source: []const u8, tok_list: *std.ArrayList(CToken)) Error!MacroSlicer {
174 var param_count: u32 = 0;
175 var param_buf: [8][]const u8 = undefined;
176
177 var tokenizer: aro.Tokenizer = .{
178 .buf = source,
179 .source = .unused,
180 .langopts = .{},
181 .splice_locs = &.{},
182 };
183 {
184 const name_tok = tokenizer.nextNoWS();
185 assert(name_tok.id == .identifier);
186 const l_paren = tokenizer.nextNoWS();
187 assert(l_paren.id == .l_paren);
188 }
189
190 while (true) {
191 const param = tokenizer.nextNoWS();
192 if (param.id == .r_paren) break;
193 assert(param.id == .identifier);
194 const slice = source[param.start..param.end];
195 param_buf[param_count] = slice;
196 param_count += 1;
197
198 const comma = tokenizer.nextNoWS();
199 if (comma.id == .r_paren) break;
200 assert(comma.id == .comma);
201 }
202
203 outer: while (true) {
204 const tok = tokenizer.next();
205 switch (tok.id) {
206 .whitespace, .comment => continue,
207 .identifier => {
208 const slice = source[tok.start..tok.end];
209 for (param_buf[0..param_count], 0..) |param, i| {
210 if (std.mem.eql(u8, param, slice)) {
211 try tok_list.append(allocator, .{
212 .id = .macro_param,
213 .source = .unused,
214 .end = @intCast(i),
215 });
216 continue :outer;
217 }
218 }
219 },
220 .hash_hash => {
221 if (tok_list.items[tok_list.items.len - 1].id == .macro_param) {
222 tok_list.items[tok_list.items.len - 1].id = .macro_param_no_expand;
223 }
224 },
225 .nl, .eof => break,
226 else => {},
227 }
228 try tok_list.append(allocator, tok);
229 }
230
231 return .{
232 .source = source,
233 .tokens = try tok_list.toOwnedSlice(allocator),
234 .params = param_count,
235 };
236}
237
238test "Macro matching" {
239 const testing = std.testing;
240 const helper = struct {
241 fn checkMacro(
242 allocator: mem.Allocator,
243 pattern_list: PatternList,
244 source: []const u8,
245 comptime expected_match: ?Impl,
246 ) !void {
247 var tok_list: std.ArrayList(CToken) = .empty;
248 defer tok_list.deinit(allocator);
249 const ms = try tokenizeMacro(allocator, source, &tok_list);
250 defer allocator.free(ms.tokens);
251
252 const matched = try pattern_list.match(ms);
253 if (expected_match) |expected| {
254 try testing.expectEqual(expected, matched);
255 } else {
256 try testing.expectEqual(@as(@TypeOf(matched), null), matched);
257 }
258 }
259 };
260 const allocator = std.testing.allocator;
261 var pattern_list = try PatternList.init(allocator);
262 defer pattern_list.deinit(allocator);
263
264 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", .F_SUFFIX);
265 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", .U_SUFFIX);
266 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", .L_SUFFIX);
267 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", .LL_SUFFIX);
268 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", .UL_SUFFIX);
269 try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", .ULL_SUFFIX);
270 try helper.checkMacro(allocator, pattern_list,
271 \\container_of(a, b, c) \
272 \\(__typeof__(b))((char *)(a) - \
273 \\ offsetof(__typeof__(*b), c))
274 , .WL_CONTAINER_OF);
275
276 try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null);
277 try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL);
278 try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL);
279 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (void)(X)", .DISCARD);
280 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((void)(X))", .DISCARD);
281 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const void)(X)", .DISCARD);
282 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const void)(X))", .DISCARD);
283 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile void)(X)", .DISCARD);
284 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile void)(X))", .DISCARD);
285 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const volatile void)(X)", .DISCARD);
286 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD);
287 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile const void)(X)", .DISCARD);
288 try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD);
289}