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