const std = @import("std"); pub const Grammar = @import("grammar.zig"); fn help(err: ?anyerror) noreturn { const stderr = std.io.getStdErr().writer(); if (err) |e| { stderr.print("error: {s}\n", .{@errorName(e)}) catch unreachable; } stderr.writeAll( \\mry [command] [options] \\ \\Commands: \\ \\ generate [grammar] [options] \\ Options: \\ -o, --output entry Output string to file \\ By default stdout will be used. \\ \\ -c, --count n Number of texts to generate. (default: 1) \\ \\ -n, --non-empty Only output texts which are non-empty. \\ \\ recognize [grammar] [options] \\ Options: \\ -i, --input entry Specify input source, if the path \\ points to a directory it will scan \\ all files in it. By default stdin will \\ be used as input. \\ \\General Options \\ -h, --help Print usage \\ ) catch unreachable; std.process.exit(@intFromBool(err != null)); } fn check(arg_or_null: ?[]const u8) []const u8 { const arg = arg_or_null orelse help(error.MissingArgument); if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) { help(null); } return arg; } fn parse_enum(T: type, arg: []const u8) T { return std.meta.stringToEnum(T, arg) orelse help(error.InvalidArgument); } fn parse_int(n: []const u8) usize { return std.fmt.parseInt(usize, n, 10) catch |e| help(e); } fn check_flags(arg: []const u8, comptime flags: []const []const u8) bool { inline for (flags) |flag| { if (std.mem.eql(u8, arg, flag)) { return true; } } return false; } pub const Mode = enum { recognize, generate, }; pub const Entry = struct { const Self = @This(); name: []const u8, file: std.fs.File, pub fn open(path: []const u8) Self { var cwd = std.fs.cwd(); return Self { .name = path, .file = cwd.openFile(path, .{}) catch |e| help(e) }; } pub fn read_file(path: []const u8, allocator: std.mem.Allocator) []const u8 { var cwd = std.fs.cwd(); var file = cwd.openFile(path, .{}) catch |e| help(e); defer file.close(); const stat = file.stat() catch |e| help(e); return file.readToEndAlloc(allocator, stat.size) catch |e| help(e); } pub fn stdin() Self { return Self { .name = "[stdin]", .file = std.io.getStdIn(), }; } pub fn stdout() Self { return Self { .name = "[stdout]", .file = std.io.getStdOut(), }; } }; pub const RecognizeArgs = struct { input: Entry, grammar: Grammar, }; pub const GenerateArgs = struct { count: usize, empty: bool, output: Entry, grammar: Grammar, }; pub const Args = union(Mode) { const Self = @This(); recognize: RecognizeArgs, generate: GenerateArgs, pub fn parse(allocator: std.mem.Allocator) Self { var args = std.process.args(); _ = args.next(); const mode = parse_enum(Mode, check(args.next())); const text = Entry.read_file(check(args.next()), allocator); defer allocator.free(text); const grammar = Grammar.parse( text, allocator ) catch |e| help(e); switch (mode) { .recognize => { var input: ?Entry = null; while (args.next()) |arg| { if (check_flags(arg, &[_][]const u8 { "-i", "--input" })) { input = Entry.open(check(args.next())); } else help(error.InvalidArgument); } return Self { .recognize = .{ .input = input orelse Entry.stdin(), .grammar = grammar, }, }; }, .generate => { var count: usize = 1; var output: ?Entry = null; var empty: bool = true; while (args.next()) |arg| { if (check_flags(arg, &[_][]const u8 { "-o", "--output" })) { output = Entry.open(check(args.next())); } else if (check_flags(arg, &[_][]const u8 { "-c", "--count" })) { count = parse_int(check(args.next())); } else if (check_flags(arg, &[_][]const u8 { "-n", "--non-empty" })) { empty = false; } else help(error.InvalidArgument); } return Self { .generate = .{ .count = count, .empty = empty, .output = output orelse Entry.stdout(), .grammar = grammar, }, }; }, } } pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { switch (self.*) { .recognize => |*rec| { rec.grammar.deinit(allocator); }, .generate => |*gen| { gen.grammar.deinit(allocator); } } } };