package main import ( "bufio" "fmt" "io" "os" "strings" ) type Context struct { stdout io.Writer stderr io.Writer stdin io.Reader } type GetCommandResult int const ( GetCommandResultOk GetCommandResult = iota GetCommandResultError ) type Command struct { command_type CommandType tokens []string } func makeCommandNone() Command { return Command{ command_type: CommandTypeNone, tokens: nil, } } func makeCommand(command_type CommandType, tokens []string) Command { if command_type == CommandTypeNone { return Command{ command_type: command_type, tokens: nil, } } else { return Command{ command_type: command_type, tokens: tokens, } } } type CommandType int const ( CommandTypeNone CommandType = iota CommandTypeExternal CommandTypeBuiltinExit CommandTypeBuiltinEcho CommandTypeBuiltinType ) func getCommand(ctx *Context) (Command, error) { fmt.Print("$ ") command_line, err := bufio.NewReader(ctx.stdin).ReadString('\n') if err != nil { return makeCommandNone(), err } command, err := parseCommand(command_line) if err != nil { fmt.Fprintln(ctx.stderr, err) return makeCommandNone(), nil } return command, nil } func tokenize(str string) []string { tokens := strings.Fields(str) return tokens } func parseCommand(command_line string) (Command, error) { tokens := tokenize(command_line) if len(tokens) == 0 { return makeCommandNone(), nil } if tokens[0] == "exit" { return makeCommand(CommandTypeBuiltinExit, tokens), nil } if tokens[0] == "echo" { return makeCommand(CommandTypeBuiltinEcho, tokens), nil } if tokens[0] == "type" { return makeCommand(CommandTypeBuiltinType, tokens), nil } return makeCommandNone(), fmt.Errorf("%s: not found", tokens[0]) } func handleCommand(ctx Context, command Command) { // fmt.Printf("handleCommand():command=%#v\n", command) switch command.command_type { case CommandTypeNone: return case CommandTypeBuiltinExit: os.Exit(0) case CommandTypeBuiltinEcho: fmt.Fprintln(ctx.stdout, strings.Join(command.tokens[1:], " ")) case CommandTypeBuiltinType: if len(command.tokens) != 2 { fmt.Fprintln(ctx.stderr, "usage: type COMMAND") return } parsed, err := parseCommand(command.tokens[1]) if err != nil { fmt.Fprintln(ctx.stderr, err) return } switch parsed.command_type { case CommandTypeNone: panic("impossible parseCommand() result") case CommandTypeExternal: fmt.Fprintf(ctx.stdout, "%s is an external command\n", parsed.tokens[0]) default: fmt.Fprintf(ctx.stdout, "%s is a shell builtin\n", parsed.tokens[0]) } } } func main() { ctx := Context{ stdout: os.Stdout, stderr: os.Stderr, stdin: os.Stdin, } for { command, err := getCommand(&ctx) if err != nil { fmt.Fprintln(os.Stderr, "Error reading input: ", err) os.Exit(1) } handleCommand(ctx, command) } }