master
   1const std = @import("std");
   2
   3pub const ModuleDefinitionType = enum {
   4    mingw,
   5};
   6
   7pub const ModuleDefinition = struct {
   8    exports: std.ArrayList(Export) = .empty,
   9    name: ?[]const u8 = null,
  10    base_address: usize = 0,
  11    arena: std.heap.ArenaAllocator,
  12    type: ModuleDefinitionType,
  13
  14    pub const Export = struct {
  15        /// This may lack mangling, such as underscore prefixing and stdcall suffixing.
  16        /// In a .def file, this is `foo` in `foo` or `bar` in `foo = bar`.
  17        name: []const u8,
  18        /// Note: This is currently only set by `fixupForImportLibraryGeneration`
  19        mangled_symbol_name: ?[]const u8,
  20        /// The external, exported name.
  21        /// In a .def file, this is `foo` in `foo = bar`.
  22        ext_name: ?[]const u8,
  23        /// In a .def file, this is `bar` in `foo == bar`.
  24        import_name: ?[]const u8,
  25        /// In a .def file, this is `bar` in `foo EXPORTAS bar`.
  26        export_as: ?[]const u8,
  27        no_name: bool,
  28        ordinal: u16,
  29        type: std.coff.ImportType,
  30        private: bool,
  31    };
  32
  33    /// Modifies `exports` such that import library generation will
  34    /// behave as expected. Based on LLVM's dlltool driver.
  35    pub fn fixupForImportLibraryGeneration(self: *ModuleDefinition, machine_type: std.coff.IMAGE.FILE.MACHINE) void {
  36        const kill_at = true;
  37        for (self.exports.items) |*e| {
  38            // If ExtName is set (if the "ExtName = Name" syntax was used), overwrite
  39            // Name with ExtName and clear ExtName. When only creating an import
  40            // library and not linking, the internal name is irrelevant. This avoids
  41            // cases where writeImportLibrary tries to transplant decoration from
  42            // symbol decoration onto ExtName.
  43            if (e.ext_name) |ext_name| {
  44                e.name = ext_name;
  45                e.ext_name = null;
  46            }
  47
  48            if (kill_at) {
  49                if (e.import_name != null or std.mem.startsWith(u8, e.name, "?"))
  50                    continue;
  51
  52                if (machine_type == .I386) {
  53                    // By making sure E.SymbolName != E.Name for decorated symbols,
  54                    // writeImportLibrary writes these symbols with the type
  55                    // IMPORT_NAME_UNDECORATE.
  56                    e.mangled_symbol_name = e.name;
  57                }
  58                // Trim off the trailing decoration. Symbols will always have a
  59                // starting prefix here (either _ for cdecl/stdcall, @ for fastcall
  60                // or ? for C++ functions). Vectorcall functions won't have any
  61                // fixed prefix, but the function base name will still be at least
  62                // one char.
  63                const name_len_without_at_suffix = std.mem.indexOfScalarPos(u8, e.name, 1, '@') orelse e.name.len;
  64                e.name = e.name[0..name_len_without_at_suffix];
  65            }
  66        }
  67    }
  68
  69    pub fn deinit(self: *const ModuleDefinition) void {
  70        self.arena.deinit();
  71    }
  72};
  73
  74pub const Diagnostics = struct {
  75    err: Error,
  76    token: Token,
  77    extra: Extra = .{ .none = {} },
  78
  79    pub const Extra = union {
  80        none: void,
  81        expected: Token.Tag,
  82    };
  83
  84    pub const Error = enum {
  85        invalid_byte,
  86        unfinished_quoted_identifier,
  87        /// `expected` is populated
  88        expected_token,
  89        expected_integer,
  90        unknown_statement,
  91        unimplemented,
  92    };
  93
  94    fn formatToken(ctx: TokenFormatContext, writer: *std.Io.Writer) std.Io.Writer.Error!void {
  95        switch (ctx.token.tag) {
  96            .eof, .invalid => return writer.writeAll(ctx.token.tag.nameForErrorDisplay()),
  97            else => return writer.writeAll(ctx.token.slice(ctx.source)),
  98        }
  99    }
 100
 101    const TokenFormatContext = struct {
 102        token: Token,
 103        source: []const u8,
 104    };
 105
 106    fn fmtToken(self: Diagnostics, source: []const u8) std.fmt.Alt(TokenFormatContext, formatToken) {
 107        return .{ .data = .{
 108            .token = self.token,
 109            .source = source,
 110        } };
 111    }
 112
 113    pub fn writeMsg(self: Diagnostics, writer: *std.Io.Writer, source: []const u8) !void {
 114        switch (self.err) {
 115            .invalid_byte => {
 116                return writer.print("invalid byte '{f}'", .{std.ascii.hexEscape(self.token.slice(source), .upper)});
 117            },
 118            .unfinished_quoted_identifier => {
 119                return writer.print("unfinished quoted identifier at '{f}', expected closing '\"'", .{self.fmtToken(source)});
 120            },
 121            .expected_token => {
 122                return writer.print("expected '{s}', got '{f}'", .{ self.extra.expected.nameForErrorDisplay(), self.fmtToken(source) });
 123            },
 124            .expected_integer => {
 125                return writer.print("expected integer, got '{f}'", .{self.fmtToken(source)});
 126            },
 127            .unimplemented => {
 128                return writer.print("support for '{f}' has not yet been implemented", .{self.fmtToken(source)});
 129            },
 130            .unknown_statement => {
 131                return writer.print("unknown/invalid statement syntax beginning with '{f}'", .{self.fmtToken(source)});
 132            },
 133        }
 134    }
 135};
 136
 137pub fn parse(
 138    allocator: std.mem.Allocator,
 139    source: [:0]const u8,
 140    machine_type: std.coff.IMAGE.FILE.MACHINE,
 141    module_definition_type: ModuleDefinitionType,
 142    diagnostics: *Diagnostics,
 143) !ModuleDefinition {
 144    var tokenizer = Tokenizer.init(source);
 145    var parser = Parser.init(&tokenizer, machine_type, module_definition_type, diagnostics);
 146
 147    return parser.parse(allocator);
 148}
 149
 150const Token = struct {
 151    tag: Tag,
 152    start: usize,
 153    end: usize,
 154
 155    pub const keywords = std.StaticStringMap(Tag).initComptime(.{
 156        .{ "BASE", .keyword_base },
 157        .{ "CONSTANT", .keyword_constant },
 158        .{ "DATA", .keyword_data },
 159        .{ "EXPORTS", .keyword_exports },
 160        .{ "EXPORTAS", .keyword_exportas },
 161        .{ "HEAPSIZE", .keyword_heapsize },
 162        .{ "LIBRARY", .keyword_library },
 163        .{ "NAME", .keyword_name },
 164        .{ "NONAME", .keyword_noname },
 165        .{ "PRIVATE", .keyword_private },
 166        .{ "STACKSIZE", .keyword_stacksize },
 167        .{ "VERSION", .keyword_version },
 168    });
 169
 170    pub const Tag = enum {
 171        invalid,
 172        eof,
 173        identifier,
 174        comma,
 175        equal,
 176        equal_equal,
 177        keyword_base,
 178        keyword_constant,
 179        keyword_data,
 180        keyword_exports,
 181        keyword_exportas,
 182        keyword_heapsize,
 183        keyword_library,
 184        keyword_name,
 185        keyword_noname,
 186        keyword_private,
 187        keyword_stacksize,
 188        keyword_version,
 189
 190        pub fn nameForErrorDisplay(self: Tag) []const u8 {
 191            return switch (self) {
 192                .invalid => "<invalid>",
 193                .eof => "<eof>",
 194                .identifier => "<identifier>",
 195                .comma => ",",
 196                .equal => "=",
 197                .equal_equal => "==",
 198                .keyword_base => "BASE",
 199                .keyword_constant => "CONSTANT",
 200                .keyword_data => "DATA",
 201                .keyword_exports => "EXPORTS",
 202                .keyword_exportas => "EXPORTAS",
 203                .keyword_heapsize => "HEAPSIZE",
 204                .keyword_library => "LIBRARY",
 205                .keyword_name => "NAME",
 206                .keyword_noname => "NONAME",
 207                .keyword_private => "PRIVATE",
 208                .keyword_stacksize => "STACKSIZE",
 209                .keyword_version => "VERSION",
 210            };
 211        }
 212    };
 213
 214    /// Returns a useful slice of the token, e.g. for quoted identifiers, this
 215    /// will return a slice without the quotes included.
 216    pub fn slice(self: Token, source: []const u8) []const u8 {
 217        return source[self.start..self.end];
 218    }
 219};
 220
 221const Tokenizer = struct {
 222    source: [:0]const u8,
 223    index: usize,
 224    error_context_token: ?Token = null,
 225
 226    pub fn init(source: [:0]const u8) Tokenizer {
 227        return .{
 228            .source = source,
 229            .index = 0,
 230        };
 231    }
 232
 233    const State = enum {
 234        start,
 235        identifier_or_keyword,
 236        quoted_identifier,
 237        comment,
 238        equal,
 239        eof_or_invalid,
 240    };
 241
 242    pub const Error = error{
 243        InvalidByte,
 244        UnfinishedQuotedIdentifier,
 245    };
 246
 247    pub fn next(self: *Tokenizer) Error!Token {
 248        var result: Token = .{
 249            .tag = undefined,
 250            .start = self.index,
 251            .end = undefined,
 252        };
 253        state: switch (State.start) {
 254            .start => switch (self.source[self.index]) {
 255                0 => continue :state .eof_or_invalid,
 256                '\r', '\n', ' ', '\t', '\x0B' => {
 257                    self.index += 1;
 258                    result.start = self.index;
 259                    continue :state .start;
 260                },
 261                ';' => continue :state .comment,
 262                '=' => continue :state .equal,
 263                ',' => {
 264                    result.tag = .comma;
 265                    self.index += 1;
 266                },
 267                '"' => continue :state .quoted_identifier,
 268                else => continue :state .identifier_or_keyword,
 269            },
 270            .comment => {
 271                self.index += 1;
 272                switch (self.source[self.index]) {
 273                    0 => continue :state .eof_or_invalid,
 274                    '\n' => {
 275                        self.index += 1;
 276                        result.start = self.index;
 277                        continue :state .start;
 278                    },
 279                    else => continue :state .comment,
 280                }
 281            },
 282            .equal => {
 283                self.index += 1;
 284                switch (self.source[self.index]) {
 285                    '=' => {
 286                        result.tag = .equal_equal;
 287                        self.index += 1;
 288                    },
 289                    else => result.tag = .equal,
 290                }
 291            },
 292            .quoted_identifier => {
 293                self.index += 1;
 294                switch (self.source[self.index]) {
 295                    0 => {
 296                        self.error_context_token = .{
 297                            .tag = .eof,
 298                            .start = self.index,
 299                            .end = self.index,
 300                        };
 301                        return error.UnfinishedQuotedIdentifier;
 302                    },
 303                    '"' => {
 304                        result.tag = .identifier;
 305                        self.index += 1;
 306
 307                        // Return the token unquoted
 308                        return .{
 309                            .tag = result.tag,
 310                            .start = result.start + 1,
 311                            .end = self.index - 1,
 312                        };
 313                    },
 314                    else => continue :state .quoted_identifier,
 315                }
 316            },
 317            .identifier_or_keyword => {
 318                self.index += 1;
 319                switch (self.source[self.index]) {
 320                    0, '=', ',', ';', '\r', '\n', ' ', '\t', '\x0B' => {
 321                        const keyword = Token.keywords.get(self.source[result.start..self.index]);
 322                        result.tag = keyword orelse .identifier;
 323                    },
 324                    else => continue :state .identifier_or_keyword,
 325                }
 326            },
 327            .eof_or_invalid => {
 328                if (self.index == self.source.len) {
 329                    return .{
 330                        .tag = .eof,
 331                        .start = self.index,
 332                        .end = self.index,
 333                    };
 334                }
 335                self.error_context_token = .{
 336                    .tag = .invalid,
 337                    .start = self.index,
 338                    .end = self.index + 1,
 339                };
 340                return error.InvalidByte;
 341            },
 342        }
 343
 344        result.end = self.index;
 345        return result;
 346    }
 347};
 348
 349test Tokenizer {
 350    try testTokenizer(
 351        \\foo
 352        \\; hello
 353        \\BASE
 354        \\"bar"
 355        \\
 356    , &.{
 357        .identifier,
 358        .keyword_base,
 359        .identifier,
 360    });
 361}
 362
 363fn testTokenizer(source: [:0]const u8, expected: []const Token.Tag) !void {
 364    var tokenizer = Tokenizer.init(source);
 365    for (expected) |expected_tag| {
 366        const token = try tokenizer.next();
 367        try std.testing.expectEqual(expected_tag, token.tag);
 368    }
 369    const last_token = try tokenizer.next();
 370    try std.testing.expectEqual(.eof, last_token.tag);
 371}
 372
 373pub const Parser = struct {
 374    tokenizer: *Tokenizer,
 375    diagnostics: *Diagnostics,
 376    lookahead_tokenizer: Tokenizer,
 377    machine_type: std.coff.IMAGE.FILE.MACHINE,
 378    module_definition_type: ModuleDefinitionType,
 379
 380    pub fn init(
 381        tokenizer: *Tokenizer,
 382        machine_type: std.coff.IMAGE.FILE.MACHINE,
 383        module_definition_type: ModuleDefinitionType,
 384        diagnostics: *Diagnostics,
 385    ) Parser {
 386        return .{
 387            .tokenizer = tokenizer,
 388            .machine_type = machine_type,
 389            .module_definition_type = module_definition_type,
 390            .diagnostics = diagnostics,
 391            .lookahead_tokenizer = undefined,
 392        };
 393    }
 394
 395    pub const Error = error{ParseError} || std.mem.Allocator.Error;
 396
 397    pub fn parse(self: *Parser, allocator: std.mem.Allocator) Error!ModuleDefinition {
 398        var module: ModuleDefinition = .{
 399            .arena = .init(allocator),
 400            .type = self.module_definition_type,
 401        };
 402        const arena = module.arena.allocator();
 403        errdefer module.deinit();
 404        while (true) {
 405            const tok = try self.nextToken();
 406            switch (tok.tag) {
 407                .eof => break,
 408                .keyword_library, .keyword_name => {
 409                    const is_library = tok.tag == .keyword_library;
 410
 411                    const name = try self.lookaheadToken();
 412                    if (name.tag != .identifier) continue;
 413                    self.commitLookahead();
 414
 415                    const base_tok = try self.lookaheadToken();
 416                    if (base_tok.tag == .keyword_base) {
 417                        self.commitLookahead();
 418
 419                        _ = try self.expectToken(.equal);
 420
 421                        module.base_address = try self.expectInteger(usize);
 422                    }
 423
 424                    // Append .dll/.exe if there's no extension
 425                    const name_slice = name.slice(self.tokenizer.source);
 426                    module.name = if (std.fs.path.extension(name_slice).len == 0)
 427                        try std.mem.concat(arena, u8, &.{ name_slice, if (is_library) ".dll" else ".exe" })
 428                    else
 429                        try arena.dupe(u8, name_slice);
 430                },
 431                .keyword_exports => {
 432                    while (true) {
 433                        var name_tok = try self.lookaheadToken();
 434                        if (name_tok.tag != .identifier) break;
 435                        self.commitLookahead();
 436
 437                        const ext_name_tok = ext_name: {
 438                            const equal = try self.lookaheadToken();
 439                            if (equal.tag != .equal) break :ext_name null;
 440                            self.commitLookahead();
 441
 442                            // The syntax is `<ext_name> = <name>`, so we need to
 443                            // swap the current name token over to ext_name and use
 444                            // this token as the name.
 445                            const ext_name_tok = name_tok;
 446                            name_tok = try self.expectToken(.identifier);
 447                            break :ext_name ext_name_tok;
 448                        };
 449
 450                        var name_needs_underscore = false;
 451                        var ext_name_needs_underscore = false;
 452                        if (self.machine_type == .I386) {
 453                            const is_decorated = isDecorated(name_tok.slice(self.tokenizer.source), self.module_definition_type);
 454                            const is_forward_target = ext_name_tok != null and std.mem.indexOfScalar(u8, name_tok.slice(self.tokenizer.source), '.') != null;
 455                            name_needs_underscore = !is_decorated and !is_forward_target;
 456
 457                            if (ext_name_tok) |ext_name| {
 458                                ext_name_needs_underscore = !isDecorated(ext_name.slice(self.tokenizer.source), self.module_definition_type);
 459                            }
 460                        }
 461
 462                        var import_name_tok: ?Token = null;
 463                        var export_as_tok: ?Token = null;
 464                        var ordinal: ?u16 = null;
 465                        var import_type: std.coff.ImportType = .CODE;
 466                        var private: bool = false;
 467                        var no_name: bool = false;
 468                        while (true) {
 469                            const arg_tok = try self.lookaheadToken();
 470                            switch (arg_tok.tag) {
 471                                .identifier => {
 472                                    const slice = arg_tok.slice(self.tokenizer.source);
 473                                    if (slice[0] != '@') break;
 474
 475                                    // foo @ 10
 476                                    if (slice.len == 1) {
 477                                        self.commitLookahead();
 478                                        ordinal = try self.expectInteger(u16);
 479                                        continue;
 480                                    }
 481                                    // foo @10
 482                                    ordinal = std.fmt.parseUnsigned(u16, slice[1..], 0) catch {
 483                                        // e.g. foo @bar, the @bar is presumed to be the start of a separate
 484                                        // export (and there could be a newline between them)
 485                                        break;
 486                                    };
 487                                    // finally safe to commit to consuming the token
 488                                    self.commitLookahead();
 489
 490                                    const noname_tok = try self.lookaheadToken();
 491                                    if (noname_tok.tag == .keyword_noname) {
 492                                        self.commitLookahead();
 493                                        no_name = true;
 494                                    }
 495                                },
 496                                .equal_equal => {
 497                                    self.commitLookahead();
 498                                    import_name_tok = try self.expectToken(.identifier);
 499                                },
 500                                .keyword_data => {
 501                                    self.commitLookahead();
 502                                    import_type = .DATA;
 503                                },
 504                                .keyword_constant => {
 505                                    self.commitLookahead();
 506                                    import_type = .CONST;
 507                                },
 508                                .keyword_private => {
 509                                    self.commitLookahead();
 510                                    private = true;
 511                                },
 512                                .keyword_exportas => {
 513                                    self.commitLookahead();
 514                                    export_as_tok = try self.expectToken(.identifier);
 515                                },
 516                                else => break,
 517                            }
 518                        }
 519
 520                        const name = if (name_needs_underscore)
 521                            try std.mem.concat(arena, u8, &.{ "_", name_tok.slice(self.tokenizer.source) })
 522                        else
 523                            try arena.dupe(u8, name_tok.slice(self.tokenizer.source));
 524
 525                        const ext_name: ?[]const u8 = if (ext_name_tok) |ext_name| if (name_needs_underscore)
 526                            try std.mem.concat(arena, u8, &.{ "_", ext_name.slice(self.tokenizer.source) })
 527                        else
 528                            try arena.dupe(u8, ext_name.slice(self.tokenizer.source)) else null;
 529
 530                        try module.exports.append(arena, .{
 531                            .name = name,
 532                            .mangled_symbol_name = null,
 533                            .ext_name = ext_name,
 534                            .import_name = if (import_name_tok) |imp_name| try arena.dupe(u8, imp_name.slice(self.tokenizer.source)) else null,
 535                            .export_as = if (export_as_tok) |export_as| try arena.dupe(u8, export_as.slice(self.tokenizer.source)) else null,
 536                            .no_name = no_name,
 537                            .ordinal = ordinal orelse 0,
 538                            .type = import_type,
 539                            .private = private,
 540                        });
 541                    }
 542                },
 543                .keyword_heapsize,
 544                .keyword_stacksize,
 545                .keyword_version,
 546                => return self.unimplemented(tok),
 547                else => {
 548                    self.diagnostics.* = .{
 549                        .err = .unknown_statement,
 550                        .token = tok,
 551                    };
 552                    return error.ParseError;
 553                },
 554            }
 555        }
 556        return module;
 557    }
 558
 559    fn isDecorated(symbol: []const u8, module_definition_type: ModuleDefinitionType) bool {
 560        // In def files, the symbols can either be listed decorated or undecorated.
 561        //
 562        // - For cdecl symbols, only the undecorated form is allowed.
 563        // - For fastcall and vectorcall symbols, both fully decorated or
 564        //   undecorated forms can be present.
 565        // - For stdcall symbols in non-MinGW environments, the decorated form is
 566        //   fully decorated with leading underscore and trailing stack argument
 567        //   size - like "_Func@0".
 568        // - In MinGW def files, a decorated stdcall symbol does not include the
 569        //   leading underscore though, like "Func@0".
 570
 571        // This function controls whether a leading underscore should be added to
 572        // the given symbol name or not. For MinGW, treat a stdcall symbol name such
 573        // as "Func@0" as undecorated, i.e. a leading underscore must be added.
 574        // For non-MinGW, look for '@' in the whole string and consider "_Func@0"
 575        // as decorated, i.e. don't add any more leading underscores.
 576        // We can't check for a leading underscore here, since function names
 577        // themselves can start with an underscore, while a second one still needs
 578        // to be added.
 579        if (std.mem.startsWith(u8, symbol, "@")) return true;
 580        if (std.mem.indexOf(u8, symbol, "@@") != null) return true;
 581        if (std.mem.startsWith(u8, symbol, "?")) return true;
 582        if (module_definition_type != .mingw and std.mem.indexOfScalar(u8, symbol, '@') != null) return true;
 583        return false;
 584    }
 585
 586    fn expectInteger(self: *Parser, T: type) Error!T {
 587        const tok = try self.nextToken();
 588        blk: {
 589            if (tok.tag != .identifier) break :blk;
 590            return std.fmt.parseUnsigned(T, tok.slice(self.tokenizer.source), 0) catch break :blk;
 591        }
 592        self.diagnostics.* = .{
 593            .err = .expected_integer,
 594            .token = tok,
 595        };
 596        return error.ParseError;
 597    }
 598
 599    fn unimplemented(self: *Parser, tok: Token) Error {
 600        self.diagnostics.* = .{
 601            .err = .unimplemented,
 602            .token = tok,
 603        };
 604        return error.ParseError;
 605    }
 606
 607    fn expectToken(self: *Parser, tag: Token.Tag) Error!Token {
 608        const tok = try self.nextToken();
 609        if (tok.tag != tag) {
 610            self.diagnostics.* = .{
 611                .err = .expected_token,
 612                .token = tok,
 613                .extra = .{ .expected = tag },
 614            };
 615            return error.ParseError;
 616        }
 617        return tok;
 618    }
 619
 620    fn nextToken(self: *Parser) Error!Token {
 621        return self.nextFromTokenizer(self.tokenizer);
 622    }
 623
 624    fn lookaheadToken(self: *Parser) Error!Token {
 625        self.lookahead_tokenizer = self.tokenizer.*;
 626        return self.nextFromTokenizer(&self.lookahead_tokenizer);
 627    }
 628
 629    fn commitLookahead(self: *Parser) void {
 630        self.tokenizer.* = self.lookahead_tokenizer;
 631    }
 632
 633    fn nextFromTokenizer(
 634        self: *Parser,
 635        tokenizer: *Tokenizer,
 636    ) Error!Token {
 637        return tokenizer.next() catch |err| {
 638            self.diagnostics.* = .{
 639                .err = switch (err) {
 640                    error.InvalidByte => .invalid_byte,
 641                    error.UnfinishedQuotedIdentifier => .unfinished_quoted_identifier,
 642                },
 643                .token = tokenizer.error_context_token.?,
 644            };
 645            return error.ParseError;
 646        };
 647    }
 648};
 649
 650test parse {
 651    const source =
 652        \\LIBRARY "foo"
 653        \\; hello
 654        \\EXPORTS
 655        \\foo @ 10
 656        \\bar @104
 657        \\baz@4
 658        \\foo == bar
 659        \\alias = function
 660        \\
 661        \\data DATA
 662        \\constant CONSTANT
 663        \\
 664    ;
 665
 666    try testParse(.AMD64, source, "foo.dll", &[_]ModuleDefinition.Export{
 667        .{
 668            .name = "foo",
 669            .mangled_symbol_name = null,
 670            .ext_name = null,
 671            .import_name = null,
 672            .export_as = null,
 673            .no_name = false,
 674            .ordinal = 10,
 675            .type = .CODE,
 676            .private = false,
 677        },
 678        .{
 679            .name = "bar",
 680            .mangled_symbol_name = null,
 681            .ext_name = null,
 682            .import_name = null,
 683            .export_as = null,
 684            .no_name = false,
 685            .ordinal = 104,
 686            .type = .CODE,
 687            .private = false,
 688        },
 689        .{
 690            .name = "baz@4",
 691            .mangled_symbol_name = null,
 692            .ext_name = null,
 693            .import_name = null,
 694            .export_as = null,
 695            .no_name = false,
 696            .ordinal = 0,
 697            .type = .CODE,
 698            .private = false,
 699        },
 700        .{
 701            .name = "foo",
 702            .mangled_symbol_name = null,
 703            .ext_name = null,
 704            .import_name = "bar",
 705            .export_as = null,
 706            .no_name = false,
 707            .ordinal = 0,
 708            .type = .CODE,
 709            .private = false,
 710        },
 711        .{
 712            .name = "function",
 713            .mangled_symbol_name = null,
 714            .ext_name = "alias",
 715            .import_name = null,
 716            .export_as = null,
 717            .no_name = false,
 718            .ordinal = 0,
 719            .type = .CODE,
 720            .private = false,
 721        },
 722        .{
 723            .name = "data",
 724            .mangled_symbol_name = null,
 725            .ext_name = null,
 726            .import_name = null,
 727            .export_as = null,
 728            .no_name = false,
 729            .ordinal = 0,
 730            .type = .DATA,
 731            .private = false,
 732        },
 733        .{
 734            .name = "constant",
 735            .mangled_symbol_name = null,
 736            .ext_name = null,
 737            .import_name = null,
 738            .export_as = null,
 739            .no_name = false,
 740            .ordinal = 0,
 741            .type = .CONST,
 742            .private = false,
 743        },
 744    });
 745
 746    try testParse(.I386, source, "foo.dll", &[_]ModuleDefinition.Export{
 747        .{
 748            .name = "_foo",
 749            .mangled_symbol_name = null,
 750            .ext_name = null,
 751            .import_name = null,
 752            .export_as = null,
 753            .no_name = false,
 754            .ordinal = 10,
 755            .type = .CODE,
 756            .private = false,
 757        },
 758        .{
 759            .name = "_bar",
 760            .mangled_symbol_name = null,
 761            .ext_name = null,
 762            .import_name = null,
 763            .export_as = null,
 764            .no_name = false,
 765            .ordinal = 104,
 766            .type = .CODE,
 767            .private = false,
 768        },
 769        .{
 770            .name = "_baz@4",
 771            .mangled_symbol_name = null,
 772            .ext_name = null,
 773            .import_name = null,
 774            .export_as = null,
 775            .no_name = false,
 776            .ordinal = 0,
 777            .type = .CODE,
 778            .private = false,
 779        },
 780        .{
 781            .name = "_foo",
 782            .mangled_symbol_name = null,
 783            .ext_name = null,
 784            .import_name = "bar",
 785            .export_as = null,
 786            .no_name = false,
 787            .ordinal = 0,
 788            .type = .CODE,
 789            .private = false,
 790        },
 791        .{
 792            .name = "_function",
 793            .mangled_symbol_name = null,
 794            .ext_name = "_alias",
 795            .import_name = null,
 796            .export_as = null,
 797            .no_name = false,
 798            .ordinal = 0,
 799            .type = .CODE,
 800            .private = false,
 801        },
 802        .{
 803            .name = "_data",
 804            .mangled_symbol_name = null,
 805            .ext_name = null,
 806            .import_name = null,
 807            .export_as = null,
 808            .no_name = false,
 809            .ordinal = 0,
 810            .type = .DATA,
 811            .private = false,
 812        },
 813        .{
 814            .name = "_constant",
 815            .mangled_symbol_name = null,
 816            .ext_name = null,
 817            .import_name = null,
 818            .export_as = null,
 819            .no_name = false,
 820            .ordinal = 0,
 821            .type = .CONST,
 822            .private = false,
 823        },
 824    });
 825
 826    try testParse(.ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{
 827        .{
 828            .name = "foo",
 829            .mangled_symbol_name = null,
 830            .ext_name = null,
 831            .import_name = null,
 832            .export_as = null,
 833            .no_name = false,
 834            .ordinal = 10,
 835            .type = .CODE,
 836            .private = false,
 837        },
 838        .{
 839            .name = "bar",
 840            .mangled_symbol_name = null,
 841            .ext_name = null,
 842            .import_name = null,
 843            .export_as = null,
 844            .no_name = false,
 845            .ordinal = 104,
 846            .type = .CODE,
 847            .private = false,
 848        },
 849        .{
 850            .name = "baz@4",
 851            .mangled_symbol_name = null,
 852            .ext_name = null,
 853            .import_name = null,
 854            .export_as = null,
 855            .no_name = false,
 856            .ordinal = 0,
 857            .type = .CODE,
 858            .private = false,
 859        },
 860        .{
 861            .name = "foo",
 862            .mangled_symbol_name = null,
 863            .ext_name = null,
 864            .import_name = "bar",
 865            .export_as = null,
 866            .no_name = false,
 867            .ordinal = 0,
 868            .type = .CODE,
 869            .private = false,
 870        },
 871        .{
 872            .name = "function",
 873            .mangled_symbol_name = null,
 874            .ext_name = "alias",
 875            .import_name = null,
 876            .export_as = null,
 877            .no_name = false,
 878            .ordinal = 0,
 879            .type = .CODE,
 880            .private = false,
 881        },
 882        .{
 883            .name = "data",
 884            .mangled_symbol_name = null,
 885            .ext_name = null,
 886            .import_name = null,
 887            .export_as = null,
 888            .no_name = false,
 889            .ordinal = 0,
 890            .type = .DATA,
 891            .private = false,
 892        },
 893        .{
 894            .name = "constant",
 895            .mangled_symbol_name = null,
 896            .ext_name = null,
 897            .import_name = null,
 898            .export_as = null,
 899            .no_name = false,
 900            .ordinal = 0,
 901            .type = .CONST,
 902            .private = false,
 903        },
 904    });
 905
 906    try testParse(.ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{
 907        .{
 908            .name = "foo",
 909            .mangled_symbol_name = null,
 910            .ext_name = null,
 911            .import_name = null,
 912            .export_as = null,
 913            .no_name = false,
 914            .ordinal = 10,
 915            .type = .CODE,
 916            .private = false,
 917        },
 918        .{
 919            .name = "bar",
 920            .mangled_symbol_name = null,
 921            .ext_name = null,
 922            .import_name = null,
 923            .export_as = null,
 924            .no_name = false,
 925            .ordinal = 104,
 926            .type = .CODE,
 927            .private = false,
 928        },
 929        .{
 930            .name = "baz@4",
 931            .mangled_symbol_name = null,
 932            .ext_name = null,
 933            .import_name = null,
 934            .export_as = null,
 935            .no_name = false,
 936            .ordinal = 0,
 937            .type = .CODE,
 938            .private = false,
 939        },
 940        .{
 941            .name = "foo",
 942            .mangled_symbol_name = null,
 943            .ext_name = null,
 944            .import_name = "bar",
 945            .export_as = null,
 946            .no_name = false,
 947            .ordinal = 0,
 948            .type = .CODE,
 949            .private = false,
 950        },
 951        .{
 952            .name = "function",
 953            .mangled_symbol_name = null,
 954            .ext_name = "alias",
 955            .import_name = null,
 956            .export_as = null,
 957            .no_name = false,
 958            .ordinal = 0,
 959            .type = .CODE,
 960            .private = false,
 961        },
 962        .{
 963            .name = "data",
 964            .mangled_symbol_name = null,
 965            .ext_name = null,
 966            .import_name = null,
 967            .export_as = null,
 968            .no_name = false,
 969            .ordinal = 0,
 970            .type = .DATA,
 971            .private = false,
 972        },
 973        .{
 974            .name = "constant",
 975            .mangled_symbol_name = null,
 976            .ext_name = null,
 977            .import_name = null,
 978            .export_as = null,
 979            .no_name = false,
 980            .ordinal = 0,
 981            .type = .CONST,
 982            .private = false,
 983        },
 984    });
 985}
 986
 987test "ntdll" {
 988    const source =
 989        \\;
 990        \\; Definition file of ntdll.dll
 991        \\; Automatic generated by gendef
 992        \\; written by Kai Tietz 2008
 993        \\;
 994        \\LIBRARY "ntdll.dll"
 995        \\EXPORTS
 996        \\RtlDispatchAPC@12
 997        \\RtlActivateActivationContextUnsafeFast@0
 998    ;
 999
1000    try testParse(.AMD64, source, "ntdll.dll", &[_]ModuleDefinition.Export{
1001        .{
1002            .name = "RtlDispatchAPC@12",
1003            .mangled_symbol_name = null,
1004            .ext_name = null,
1005            .import_name = null,
1006            .export_as = null,
1007            .no_name = false,
1008            .ordinal = 0,
1009            .type = .CODE,
1010            .private = false,
1011        },
1012        .{
1013            .name = "RtlActivateActivationContextUnsafeFast@0",
1014            .mangled_symbol_name = null,
1015            .ext_name = null,
1016            .import_name = null,
1017            .export_as = null,
1018            .no_name = false,
1019            .ordinal = 0,
1020            .type = .CODE,
1021            .private = false,
1022        },
1023    });
1024}
1025
1026fn testParse(machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8, expected_module_name: []const u8, expected_exports: []const ModuleDefinition.Export) !void {
1027    var diagnostics: Diagnostics = undefined;
1028    const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {
1029        error.OutOfMemory => |e| return e,
1030        error.ParseError => {
1031            const stderr, _ = std.debug.lockStderrWriter(&.{});
1032            defer std.debug.unlockStderrWriter();
1033            try diagnostics.writeMsg(stderr, source);
1034            try stderr.writeByte('\n');
1035            return err;
1036        },
1037    };
1038    defer module.deinit();
1039
1040    try std.testing.expectEqualStrings(expected_module_name, module.name orelse "");
1041    try std.testing.expectEqual(expected_exports.len, module.exports.items.len);
1042    for (expected_exports, module.exports.items) |expected, actual| {
1043        try std.testing.expectEqualStrings(expected.name, actual.name);
1044        try std.testing.expectEqualStrings(expected.export_as orelse "", actual.export_as orelse "");
1045        try std.testing.expectEqualStrings(expected.ext_name orelse "", actual.ext_name orelse "");
1046        try std.testing.expectEqualStrings(expected.import_name orelse "", actual.import_name orelse "");
1047        try std.testing.expectEqualStrings(expected.mangled_symbol_name orelse "", actual.mangled_symbol_name orelse "");
1048        try std.testing.expectEqual(expected.ordinal, actual.ordinal);
1049        try std.testing.expectEqual(expected.no_name, actual.no_name);
1050        try std.testing.expectEqual(expected.private, actual.private);
1051        try std.testing.expectEqual(expected.type, actual.type);
1052    }
1053}
1054
1055test "parse errors" {
1056    for (&[_]std.coff.IMAGE.FILE.MACHINE{ .AMD64, .I386, .ARMNT, .ARM64 }) |machine_type| {
1057        try testParseErrorMsg("invalid byte '\\x00'", machine_type, "LIBRARY \x00");
1058        try testParseErrorMsg("unfinished quoted identifier at '<eof>', expected closing '\"'", machine_type, "LIBRARY \"foo");
1059        try testParseErrorMsg("expected '=', got 'foo'", machine_type, "LIBRARY foo BASE foo");
1060        try testParseErrorMsg("expected integer, got 'foo'", machine_type, "EXPORTS foo @ foo");
1061        try testParseErrorMsg("support for 'HEAPSIZE' has not yet been implemented", machine_type, "HEAPSIZE");
1062        try testParseErrorMsg("unknown/invalid statement syntax beginning with 'LIB'", machine_type, "LIB");
1063    }
1064}
1065
1066fn testParseErrorMsg(expected_msg: []const u8, machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8) !void {
1067    var diagnostics: Diagnostics = undefined;
1068    _ = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {
1069        error.OutOfMemory => |e| return e,
1070        error.ParseError => {
1071            var buf: [256]u8 = undefined;
1072            var writer: std.Io.Writer = .fixed(&buf);
1073            try diagnostics.writeMsg(&writer, source);
1074            try std.testing.expectEqualStrings(expected_msg, writer.buffered());
1075            return;
1076        },
1077    };
1078    return error.UnexpectedSuccess;
1079}