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