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 ) 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 := make([]string, 2) tokens = strings.Fields(str) return tokens } func parseCommand(command_line string) (Command, error) { tokens := tokenize(command_line) if tokens[0] == "exit" { return makeCommand(CommandTypeBuiltinExit, tokens), nil } if tokens[0] == "echo" { return makeCommand(CommandTypeBuiltinEcho, tokens), nil } return makeCommandNone(), fmt.Errorf("%s: command not found", tokens[0]) } func handleCommand(ctx Context, command Command) { _ = ctx switch command.command_type { case CommandTypeNone: return case CommandTypeBuiltinExit: os.Exit(0) case CommandTypeBuiltinEcho: fmt.Fprintln(ctx.stdout, strings.Join(command.tokens[1:], " ")) } } 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) } }