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: \\ -e, --entry label Name of the entry point. (default: main) \\ \\ -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. \\ \\ -m, --min-length n Minimum length of sentential string. \\ \\ recognize [grammar] [options] \\ Options: \\ -e, --entry label Name of the entry point. (default: main) \\ \\ -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, writer: bool) Self { var cwd = std.fs.cwd(); return Self { .name = path, .file = (if (writer) cwd.createFile(path, .{}) else 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, entry: []const u8, }; pub const GenerateArgs = struct { count: usize, min_length: usize, output: Entry, grammar: Grammar, entry: []const u8, }; 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); switch (mode) { .recognize => { var input: ?Entry = null; var entry: []const u8 = "main"; while (args.next()) |arg| { if (check_flags(arg, &[_][]const u8 { "-i", "--input" })) { input = Entry.open(check(args.next()), false); } else if (check_flags(arg, &[_][]const u8 { "-e", "--entry" })) { entry = check(args.next()); } else help(error.InvalidArgument); } const grammar = Grammar.parse( entry, text, allocator ) catch |e| help(e); return Self { .recognize = .{ .input = input orelse Entry.stdin(), .grammar = grammar, .entry = entry, }, }; }, .generate => { var count: usize = 1; var output: ?Entry = null; var min_length: usize = 0; var entry: []const u8 = "main"; while (args.next()) |arg| { if (check_flags(arg, &[_][]const u8 { "-o", "--output" })) { output = Entry.open(check(args.next()), true); } 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" })) { min_length = 1; } else if (check_flags(arg, &[_][]const u8 { "-m", "--min-length" })) { min_length = parse_int(check(args.next())); } else if (check_flags(arg, &[_][]const u8 { "-e", "--entry" })) { entry = check(args.next()); } else help(error.InvalidArgument); } const grammar = Grammar.parse( entry, text, allocator ) catch |e| help(e); return Self { .generate = .{ .count = count, .min_length = min_length, .output = output orelse Entry.stdout(), .grammar = grammar, .entry = entry, }, }; }, } } pub fn deinit(self: *Self) void { switch (self.*) { .recognize => |*rec| { rec.grammar.deinit(); }, .generate => |*gen| { gen.grammar.deinit(); } } } };