1
1
const std = @import ("std" );
2
- const CodePage = @import ("code_pages.zig" ).CodePage ;
2
+ const code_pages = @import ("code_pages.zig" );
3
+ const SupportedCodePage = code_pages .SupportedCodePage ;
3
4
const lang = @import ("lang.zig" );
4
5
const res = @import ("res.zig" );
5
6
const Allocator = std .mem .Allocator ;
@@ -14,6 +15,8 @@ pub const usage_string_after_command_name =
14
15
\\The sequence -- can be used to signify when to stop parsing options.
15
16
\\This is necessary when the input path begins with a forward slash.
16
17
\\
18
+ \\Supported option prefixes are /, -, and --, so e.g. /h, -h, and --h all work.
19
+ \\
17
20
\\Supported Win32 RC Options:
18
21
\\ /?, /h Print this help and exit.
19
22
\\ /v Verbose (print progress messages).
@@ -56,8 +59,6 @@ pub const usage_string_after_command_name =
56
59
\\ the .rc includes or otherwise depends on.
57
60
\\ /:depfile-fmt <value> Output format of the depfile, if /:depfile is set.
58
61
\\ json (default) A top-level JSON array of paths
59
- \\ /:mingw-includes <path> Path to a directory containing MinGW include files. If
60
- \\ not specified, bundled MinGW include files will be used.
61
62
\\
62
63
\\Note: For compatibility reasons, all custom options start with :
63
64
\\
@@ -136,7 +137,7 @@ pub const Options = struct {
136
137
ignore_include_env_var : bool = false ,
137
138
preprocess : Preprocess = .yes ,
138
139
default_language_id : ? u16 = null ,
139
- default_code_page : ? CodePage = null ,
140
+ default_code_page : ? SupportedCodePage = null ,
140
141
verbose : bool = false ,
141
142
symbols : std .StringArrayHashMapUnmanaged (SymbolValue ) = .empty ,
142
143
null_terminate_string_table_strings : bool = false ,
@@ -148,7 +149,6 @@ pub const Options = struct {
148
149
auto_includes : AutoIncludes = .any ,
149
150
depfile_path : ? []const u8 = null ,
150
151
depfile_fmt : DepfileFormat = .json ,
151
- mingw_includes_dir : ? []const u8 = null ,
152
152
153
153
pub const AutoIncludes = enum { any , msvc , gnu , none };
154
154
pub const DepfileFormat = enum { json };
@@ -243,9 +243,6 @@ pub const Options = struct {
243
243
if (self .depfile_path ) | depfile_path | {
244
244
self .allocator .free (depfile_path );
245
245
}
246
- if (self .mingw_includes_dir ) | mingw_includes_dir | {
247
- self .allocator .free (mingw_includes_dir );
248
- }
249
246
}
250
247
251
248
pub fn dumpVerbose (self : * const Options , writer : anytype ) ! void {
@@ -358,6 +355,29 @@ pub const Arg = struct {
358
355
};
359
356
}
360
357
358
+ pub fn looksLikeFilepath (self : Arg ) bool {
359
+ const meets_min_requirements = self .prefix == .slash and isSupportedInputExtension (std .fs .path .extension (self .full ));
360
+ if (! meets_min_requirements ) return false ;
361
+
362
+ const could_be_fo_option = could_be_fo_option : {
363
+ var window_it = std .mem .window (u8 , self .full [1.. ], 2 , 1 );
364
+ while (window_it .next ()) | window | {
365
+ if (std .ascii .eqlIgnoreCase (window , "fo" )) break :could_be_fo_option true ;
366
+ // If we see '/' before "fo", then it's not possible for this to be a valid
367
+ // `/fo` option.
368
+ if (window [0 ] == '/' ) break ;
369
+ }
370
+ break :could_be_fo_option false ;
371
+ };
372
+ if (! could_be_fo_option ) return true ;
373
+
374
+ // It's still possible for a file path to look like a /fo option but not actually
375
+ // be one, e.g. `/foo/bar.rc`. As a last ditch effort to reduce false negatives,
376
+ // check if the file path exists and, if so, then we ignore the 'could be /fo option'-ness
377
+ std .fs .accessAbsolute (self .full , .{}) catch return false ;
378
+ return true ;
379
+ }
380
+
361
381
pub const Value = struct {
362
382
slice : []const u8 ,
363
383
index_increment : u2 = 1 ,
@@ -432,6 +452,16 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
432
452
}
433
453
}
434
454
455
+ const args_remaining = args .len - arg_i ;
456
+ if (args_remaining <= 2 and arg .looksLikeFilepath ()) {
457
+ var err_details = Diagnostics.ErrorDetails { .type = .note , .print_args = true , .arg_index = arg_i };
458
+ var msg_writer = err_details .msg .writer (allocator );
459
+ try msg_writer .writeAll ("this argument was inferred to be a filepath, so argument parsing was terminated" );
460
+ try diagnostics .append (err_details );
461
+
462
+ break ;
463
+ }
464
+
435
465
while (arg .name ().len > 0 ) {
436
466
const arg_name = arg .name ();
437
467
// Note: These cases should be in order from longest to shortest, since
@@ -440,24 +470,6 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
440
470
if (std .ascii .startsWithIgnoreCase (arg_name , ":no-preprocess" )) {
441
471
options .preprocess = .no ;
442
472
arg .name_offset += ":no-preprocess" .len ;
443
- } else if (std .ascii .startsWithIgnoreCase (arg_name , ":mingw-includes" )) {
444
- const value = arg .value (":mingw-includes" .len , arg_i , args ) catch {
445
- var err_details = Diagnostics.ErrorDetails { .arg_index = arg_i , .arg_span = arg .missingSpan () };
446
- var msg_writer = err_details .msg .writer (allocator );
447
- try msg_writer .print ("missing value after {s}{s} option" , .{ arg .prefixSlice (), arg .optionWithoutPrefix (":mingw-includes" .len ) });
448
- try diagnostics .append (err_details );
449
- arg_i += 1 ;
450
- break :next_arg ;
451
- };
452
- if (options .mingw_includes_dir ) | overwritten_path | {
453
- allocator .free (overwritten_path );
454
- options .mingw_includes_dir = null ;
455
- }
456
- const path = try allocator .dupe (u8 , value .slice );
457
- errdefer allocator .free (path );
458
- options .mingw_includes_dir = path ;
459
- arg_i += value .index_increment ;
460
- continue :next_arg ;
461
473
} else if (std .ascii .startsWithIgnoreCase (arg_name , ":auto-includes" )) {
462
474
const value = arg .value (":auto-includes" .len , arg_i , args ) catch {
463
475
var err_details = Diagnostics.ErrorDetails { .arg_index = arg_i , .arg_span = arg .missingSpan () };
@@ -769,7 +781,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
769
781
arg_i += value .index_increment ;
770
782
continue :next_arg ;
771
783
};
772
- options .default_code_page = CodePage .getByIdentifierEnsureSupported (code_page_id ) catch | err | switch (err ) {
784
+ options .default_code_page = code_pages .getByIdentifierEnsureSupported (code_page_id ) catch | err | switch (err ) {
773
785
error .InvalidCodePage = > {
774
786
var err_details = Diagnostics.ErrorDetails { .arg_index = arg_i , .arg_span = value .argSpan (arg ) };
775
787
var msg_writer = err_details .msg .writer (allocator );
@@ -782,7 +794,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
782
794
var err_details = Diagnostics.ErrorDetails { .arg_index = arg_i , .arg_span = value .argSpan (arg ) };
783
795
var msg_writer = err_details .msg .writer (allocator );
784
796
try msg_writer .print ("unsupported code page: {s} (id={})" , .{
785
- @tagName (CodePage .getByIdentifier (code_page_id ) catch unreachable ),
797
+ @tagName (code_pages .getByIdentifier (code_page_id ) catch unreachable ),
786
798
code_page_id ,
787
799
});
788
800
try diagnostics .append (err_details );
@@ -900,18 +912,20 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
900
912
901
913
const positionals = args [arg_i .. ];
902
914
903
- if (positionals .len < 1 ) {
915
+ if (positionals .len == 0 ) {
904
916
var err_details = Diagnostics.ErrorDetails { .print_args = false , .arg_index = arg_i };
905
917
var msg_writer = err_details .msg .writer (allocator );
906
918
try msg_writer .writeAll ("missing input filename" );
907
919
try diagnostics .append (err_details );
908
920
909
- const last_arg = args [args .len - 1 ];
910
- if (arg_i > 0 and last_arg .len > 0 and last_arg [0 ] == '/' and std .ascii .endsWithIgnoreCase (last_arg , ".rc" )) {
911
- var note_details = Diagnostics.ErrorDetails { .type = .note , .print_args = true , .arg_index = arg_i - 1 };
912
- var note_writer = note_details .msg .writer (allocator );
913
- try note_writer .writeAll ("if this argument was intended to be the input filename, then -- should be specified in front of it to exclude it from option parsing" );
914
- try diagnostics .append (note_details );
921
+ if (args .len > 0 ) {
922
+ const last_arg = args [args .len - 1 ];
923
+ if (arg_i > 0 and last_arg .len > 0 and last_arg [0 ] == '/' and std .ascii .endsWithIgnoreCase (last_arg , ".rc" )) {
924
+ var note_details = Diagnostics.ErrorDetails { .type = .note , .print_args = true , .arg_index = arg_i - 1 };
925
+ var note_writer = note_details .msg .writer (allocator );
926
+ try note_writer .writeAll ("if this argument was intended to be the input filename, then -- should be specified in front of it to exclude it from option parsing" );
927
+ try diagnostics .append (note_details );
928
+ }
915
929
}
916
930
917
931
// This is a fatal enough problem to justify an early return, since
@@ -969,6 +983,12 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
969
983
return options ;
970
984
}
971
985
986
+ pub fn isSupportedInputExtension (ext : []const u8 ) bool {
987
+ if (std .ascii .eqlIgnoreCase (ext , ".rc" )) return true ;
988
+ if (std .ascii .eqlIgnoreCase (ext , ".rcpp" )) return true ;
989
+ return false ;
990
+ }
991
+
972
992
/// Returns true if the str is a valid C identifier for use in a #define/#undef macro
973
993
pub fn isValidIdentifier (str : []const u8 ) bool {
974
994
for (str , 0.. ) | c , i | switch (c ) {
@@ -1271,6 +1291,43 @@ test "parse errors: basic" {
1271
1291
);
1272
1292
}
1273
1293
1294
+ test "inferred absolute filepaths" {
1295
+ {
1296
+ var options = try testParseWarning (&.{ "/fo" , "foo.res" , "/home/absolute/path.rc" },
1297
+ \\<cli>: note: this argument was inferred to be a filepath, so argument parsing was terminated
1298
+ \\ ... /home/absolute/path.rc
1299
+ \\ ^~~~~~~~~~~~~~~~~~~~~~
1300
+ \\
1301
+ );
1302
+ defer options .deinit ();
1303
+ }
1304
+ {
1305
+ var options = try testParseWarning (&.{ "/home/absolute/path.rc" , "foo.res" },
1306
+ \\<cli>: note: this argument was inferred to be a filepath, so argument parsing was terminated
1307
+ \\ ... /home/absolute/path.rc ...
1308
+ \\ ^~~~~~~~~~~~~~~~~~~~~~
1309
+ \\
1310
+ );
1311
+ defer options .deinit ();
1312
+ }
1313
+ {
1314
+ // Only the last two arguments are checked, so the /h is parsed as an option
1315
+ var options = try testParse (&.{ "/home/absolute/path.rc" , "foo.rc" , "foo.res" });
1316
+ defer options .deinit ();
1317
+
1318
+ try std .testing .expect (options .print_help_and_exit );
1319
+ }
1320
+ {
1321
+ var options = try testParse (&.{ "/xvFO/some/absolute/path.res" , "foo.rc" });
1322
+ defer options .deinit ();
1323
+
1324
+ try std .testing .expectEqual (true , options .verbose );
1325
+ try std .testing .expectEqual (true , options .ignore_include_env_var );
1326
+ try std .testing .expectEqualStrings ("foo.rc" , options .input_source .filename );
1327
+ try std .testing .expectEqualStrings ("/some/absolute/path.res" , options .output_source .filename );
1328
+ }
1329
+ }
1330
+
1274
1331
test "parse errors: /ln" {
1275
1332
try testParseError (&.{ "/ln" , "invalid" , "foo.rc" },
1276
1333
\\<cli>: error: invalid language tag: invalid
0 commit comments