123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "os"
  7. "strings"
  8. )
  9. type Context struct {
  10. stdout io.Writer
  11. stderr io.Writer
  12. stdin io.Reader
  13. }
  14. type GetCommandResult int
  15. const (
  16. GetCommandResultOk GetCommandResult = iota
  17. GetCommandResultError
  18. )
  19. type Command struct {
  20. command_type CommandType
  21. tokens []string
  22. }
  23. func makeCommandNone() Command {
  24. return Command{
  25. command_type: CommandTypeNone,
  26. tokens: nil,
  27. }
  28. }
  29. func makeCommand(command_type CommandType, tokens []string) Command {
  30. if command_type == CommandTypeNone {
  31. return Command{
  32. command_type: command_type,
  33. tokens: nil,
  34. }
  35. } else {
  36. return Command{
  37. command_type: command_type,
  38. tokens: tokens,
  39. }
  40. }
  41. }
  42. type CommandType int
  43. const (
  44. CommandTypeNone CommandType = iota
  45. CommandTypeExternal
  46. CommandTypeBuiltinExit
  47. CommandTypeBuiltinEcho
  48. CommandTypeBuiltinType
  49. )
  50. func getCommand(ctx *Context) (Command, error) {
  51. fmt.Print("$ ")
  52. command_line, err := bufio.NewReader(ctx.stdin).ReadString('\n')
  53. if err != nil {
  54. return makeCommandNone(), err
  55. }
  56. command, err := parseCommand(command_line)
  57. if err != nil {
  58. fmt.Fprintln(ctx.stderr, err)
  59. return makeCommandNone(), nil
  60. }
  61. return command, nil
  62. }
  63. func tokenize(str string) []string {
  64. tokens := strings.Fields(str)
  65. return tokens
  66. }
  67. func parseCommand(command_line string) (Command, error) {
  68. tokens := tokenize(command_line)
  69. if len(tokens) == 0 {
  70. return makeCommandNone(), nil
  71. }
  72. if tokens[0] == "exit" {
  73. return makeCommand(CommandTypeBuiltinExit, tokens), nil
  74. }
  75. if tokens[0] == "echo" {
  76. return makeCommand(CommandTypeBuiltinEcho, tokens), nil
  77. }
  78. if tokens[0] == "type" {
  79. return makeCommand(CommandTypeBuiltinType, tokens), nil
  80. }
  81. return makeCommandNone(), fmt.Errorf("%s: not found", tokens[0])
  82. }
  83. func handleCommand(ctx Context, command Command) {
  84. // fmt.Printf("handleCommand():command=%#v\n", command)
  85. switch command.command_type {
  86. case CommandTypeNone:
  87. return
  88. case CommandTypeBuiltinExit:
  89. os.Exit(0)
  90. case CommandTypeBuiltinEcho:
  91. fmt.Fprintln(ctx.stdout, strings.Join(command.tokens[1:], " "))
  92. case CommandTypeBuiltinType:
  93. if len(command.tokens) != 2 {
  94. fmt.Fprintln(ctx.stderr, "usage: type COMMAND")
  95. return
  96. }
  97. parsed, err := parseCommand(command.tokens[1])
  98. if err != nil {
  99. fmt.Fprintln(ctx.stderr, err)
  100. return
  101. }
  102. switch parsed.command_type {
  103. case CommandTypeNone:
  104. panic("impossible parseCommand() result")
  105. case CommandTypeExternal:
  106. fmt.Fprintf(ctx.stdout, "%s is an external command\n", parsed.tokens[0])
  107. default:
  108. fmt.Fprintf(ctx.stdout, "%s is a shell builtin\n", parsed.tokens[0])
  109. }
  110. }
  111. }
  112. func main() {
  113. ctx := Context{
  114. stdout: os.Stdout,
  115. stderr: os.Stderr,
  116. stdin: os.Stdin,
  117. }
  118. for {
  119. command, err := getCommand(&ctx)
  120. if err != nil {
  121. fmt.Fprintln(os.Stderr, "Error reading input: ", err)
  122. os.Exit(1)
  123. }
  124. handleCommand(ctx, command)
  125. }
  126. }