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}