123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "os"
  7. "os/exec"
  8. "strings"
  9. )
  10. type ExitStatus uint16
  11. type Context struct {
  12. stdout io.Writer
  13. stderr io.Writer
  14. stdin io.Reader
  15. }
  16. func makeContext() Context {
  17. return Context{
  18. stdout: os.Stdout,
  19. stderr: os.Stderr,
  20. stdin: os.Stdin,
  21. }
  22. }
  23. type GetCommandResult int
  24. const (
  25. GetCommandResultOk GetCommandResult = iota
  26. GetCommandResultError
  27. )
  28. type Command struct {
  29. command_type CommandType
  30. args []string
  31. exec_path string
  32. name string
  33. }
  34. func makeCommandNone() Command {
  35. return Command{
  36. command_type: CommandTypeNone,
  37. args: nil,
  38. exec_path: "",
  39. name: "",
  40. }
  41. }
  42. func makeCommandBuiltin(command_type CommandType, tokens []string) Command {
  43. switch command_type {
  44. case CommandTypeNone:
  45. panic("invalid usage")
  46. case CommandTypeExternal:
  47. panic("invalid usage")
  48. default:
  49. if len(tokens) < 1 {
  50. panic("assertion failed: token list must not be empty")
  51. }
  52. return Command{
  53. command_type: command_type,
  54. name: tokens[0],
  55. args: tokens[1:],
  56. }
  57. }
  58. }
  59. func makeCommandExternal(tokens []string, exec_path string) Command {
  60. if len(tokens) < 1 {
  61. panic("assertion failed: token list must not be empty")
  62. }
  63. return Command{
  64. command_type: CommandTypeExternal,
  65. name: tokens[0],
  66. args: tokens[1:],
  67. exec_path: exec_path,
  68. }
  69. }
  70. type CommandType int
  71. const (
  72. CommandTypeNone CommandType = iota
  73. CommandTypeExternal
  74. CommandTypeBuiltinExit
  75. CommandTypeBuiltinEcho
  76. CommandTypeBuiltinType
  77. CommandTypeBuiltinPwd
  78. )
  79. func getCommand(ctx *Context) (Command, error) {
  80. fmt.Print("$ ")
  81. command_line, err := bufio.NewReader(ctx.stdin).ReadString('\n')
  82. if err != nil {
  83. return makeCommandNone(), err
  84. }
  85. command, err := parseCommand(command_line)
  86. // fmt.Printf("getCommand():command=%#v\n", command)
  87. if err != nil {
  88. fmt.Fprintln(ctx.stderr, err)
  89. return makeCommandNone(), nil
  90. }
  91. return command, nil
  92. }
  93. func tokenize(str string) []string {
  94. tokens := strings.Fields(str)
  95. return tokens
  96. }
  97. type ParseCommandError struct {
  98. code ParseCommandErrorCode
  99. value string
  100. }
  101. type ParseCommandErrorCode int
  102. const (
  103. ParseCommandErrorCodeNotFound ParseCommandErrorCode = iota
  104. )
  105. func (e ParseCommandError) Error() string {
  106. switch e.code {
  107. case ParseCommandErrorCodeNotFound:
  108. return fmt.Sprintf("%s: not found", e.value)
  109. default:
  110. return fmt.Sprintf("unknown ParseCommandErrorCode code: %d .value=%q", e.code, e.value)
  111. }
  112. }
  113. func parseCommand(command_line string) (Command, error) {
  114. tokens := tokenize(command_line)
  115. if len(tokens) == 0 {
  116. return makeCommandNone(), nil
  117. }
  118. if tokens[0] == "exit" {
  119. return makeCommandBuiltin(CommandTypeBuiltinExit, tokens), nil
  120. }
  121. if tokens[0] == "pwd" {
  122. return makeCommandBuiltin(CommandTypeBuiltinPwd, tokens), nil
  123. }
  124. if tokens[0] == "echo" {
  125. return makeCommandBuiltin(CommandTypeBuiltinEcho, tokens), nil
  126. }
  127. if tokens[0] == "type" {
  128. return makeCommandBuiltin(CommandTypeBuiltinType, tokens), nil
  129. }
  130. exec_path, err := exec.LookPath(tokens[0])
  131. if err != nil {
  132. return makeCommandNone(), ParseCommandError{ParseCommandErrorCodeNotFound, tokens[0]}
  133. }
  134. return makeCommandExternal(tokens, exec_path), nil
  135. }
  136. func handleCommand(ctx Context, command Command) ExitStatus {
  137. // fmt.Printf("handleCommand():command=%#v\n", command)
  138. switch command.command_type {
  139. case CommandTypeNone:
  140. return 0
  141. case CommandTypeBuiltinExit:
  142. os.Exit(0)
  143. case CommandTypeBuiltinEcho:
  144. fmt.Fprintln(ctx.stdout, strings.Join(command.args, " "))
  145. return 0
  146. case CommandTypeBuiltinPwd:
  147. if len(command.args) != 0 {
  148. fmt.Fprintln(ctx.stderr, "usage: pwd")
  149. return 2
  150. }
  151. cwd_path, err := os.Getwd()
  152. if err != nil {
  153. panic("error getting current working directory")
  154. }
  155. fmt.Fprintln(ctx.stdout, cwd_path)
  156. return 0
  157. case CommandTypeBuiltinType:
  158. if len(command.args) != 1 {
  159. fmt.Fprintln(ctx.stderr, "usage: type COMMAND")
  160. return 2
  161. }
  162. parsed, err := parseCommand(command.args[0])
  163. if err != nil {
  164. fmt.Fprintln(ctx.stderr, err)
  165. return 0
  166. }
  167. switch parsed.command_type {
  168. case CommandTypeNone:
  169. panic("impossible parseCommand() result")
  170. case CommandTypeExternal:
  171. fmt.Fprintf(ctx.stdout, "%s is %s\n", parsed.name, parsed.exec_path)
  172. default:
  173. fmt.Fprintf(ctx.stdout, "%s is a shell builtin\n", parsed.name)
  174. }
  175. return 0
  176. case CommandTypeExternal:
  177. cmd := exec.Command(command.exec_path, command.args...)
  178. cmd.Args[0] = command.name
  179. cmd.Stdout = ctx.stdout
  180. cmd.Stderr = ctx.stderr
  181. cmd.Stdin = ctx.stdin
  182. err := cmd.Run()
  183. if err != nil {
  184. if exiterr, ok := err.(*exec.ExitError); ok {
  185. return ExitStatus(exiterr.ExitCode())
  186. // fmt.Printf("handleCommand():exiterr=%#v\n", exiterr.ExitCode())
  187. } else {
  188. fmt.Printf("handleCommand():err=%#v\n", err)
  189. return ExitStatus(65535)
  190. }
  191. }
  192. return 0
  193. default:
  194. panic("unknown command type")
  195. }
  196. return ExitStatus(65535)
  197. }
  198. func main() {
  199. ctx := makeContext()
  200. for {
  201. command, err := getCommand(&ctx)
  202. if err != nil {
  203. fmt.Fprintln(os.Stderr, "Error reading input: ", err)
  204. os.Exit(1)
  205. }
  206. _ = handleCommand(ctx, command)
  207. }
  208. }