| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- package main
-
- import (
- "bufio"
- "fmt"
- "io"
- "os"
- "os/exec"
- "strings"
- )
-
- type ExitStatus uint16
-
- type Context struct {
- stdout io.Writer
- stderr io.Writer
- stdin io.Reader
- }
-
- func makeContext() Context {
- return Context{
- stdout: os.Stdout,
- stderr: os.Stderr,
- stdin: os.Stdin,
- }
- }
-
- type GetCommandResult int
-
- const (
- GetCommandResultOk GetCommandResult = iota
- GetCommandResultError
- )
-
- type Command struct {
- command_type CommandType
- args []string
- exec_path string
- name string
- }
-
- func makeCommandNone() Command {
- return Command{
- command_type: CommandTypeNone,
- args: nil,
- exec_path: "",
- name: "",
- }
- }
-
- func makeCommandBuiltin(command_type CommandType, tokens []string) Command {
- switch command_type {
- case CommandTypeNone:
- panic("invalid usage")
- case CommandTypeExternal:
- panic("invalid usage")
- default:
- if len(tokens) < 1 {
- panic("assertion failed: token list must not be empty")
- }
- return Command{
- command_type: command_type,
- name: tokens[0],
- args: tokens[1:],
- }
- }
- }
-
- func makeCommandExternal(tokens []string, exec_path string) Command {
- if len(tokens) < 1 {
- panic("assertion failed: token list must not be empty")
- }
- return Command{
- command_type: CommandTypeExternal,
- name: tokens[0],
- args: tokens[1:],
- exec_path: exec_path,
- }
- }
-
- type CommandType int
-
- const (
- CommandTypeNone CommandType = iota
- CommandTypeExternal
- CommandTypeBuiltinExit
- CommandTypeBuiltinEcho
- CommandTypeBuiltinType
- CommandTypeBuiltinPwd
- CommandTypeBuiltinCd
- )
-
- 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)
- // fmt.Printf("getCommand():command=%#v\n", command)
- 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
- }
-
- type ParseCommandError struct {
- code ParseCommandErrorCode
- value string
- }
- type ParseCommandErrorCode int
-
- const (
- ParseCommandErrorCodeNotFound ParseCommandErrorCode = iota
- )
-
- func (e ParseCommandError) Error() string {
- switch e.code {
- case ParseCommandErrorCodeNotFound:
- return fmt.Sprintf("%s: not found", e.value)
- default:
- return fmt.Sprintf("unknown ParseCommandErrorCode code: %d .value=%q", e.code, e.value)
- }
- }
-
- func parseCommand(command_line string) (Command, error) {
- tokens := tokenize(command_line)
- if len(tokens) == 0 {
- return makeCommandNone(), nil
- }
- if tokens[0] == "exit" {
- return makeCommandBuiltin(CommandTypeBuiltinExit, tokens), nil
- }
- if tokens[0] == "pwd" {
- return makeCommandBuiltin(CommandTypeBuiltinPwd, tokens), nil
- }
- if tokens[0] == "cd" {
- return makeCommandBuiltin(CommandTypeBuiltinCd, tokens), nil
- }
- if tokens[0] == "echo" {
- return makeCommandBuiltin(CommandTypeBuiltinEcho, tokens), nil
- }
- if tokens[0] == "type" {
- return makeCommandBuiltin(CommandTypeBuiltinType, tokens), nil
- }
- exec_path, err := exec.LookPath(tokens[0])
- if err != nil {
- return makeCommandNone(), ParseCommandError{ParseCommandErrorCodeNotFound, tokens[0]}
- }
- return makeCommandExternal(tokens, exec_path), nil
- }
-
- func bltnChdir(ctx Context, path string) ExitStatus {
- err := os.Chdir(path)
- if err != nil {
- fmt.Fprintf(ctx.stderr, "cd: %s: No such file or directory\n", path)
- return 4
- }
- return 0
- }
-
- func handleCommand(ctx Context, command Command) ExitStatus {
- // fmt.Printf("handleCommand():command=%#v\n", command)
- switch command.command_type {
-
- case CommandTypeNone:
- return 0
-
- case CommandTypeBuiltinExit:
- os.Exit(0)
-
- case CommandTypeBuiltinEcho:
- fmt.Fprintln(ctx.stdout, strings.Join(command.args, " "))
- return 0
-
- case CommandTypeBuiltinCd:
- switch len(command.args) {
- case 0:
- home_path := os.Getenv("HOME")
- if len(home_path) == 0 {
- fmt.Fprintln(ctx.stderr, "error: $HOME environment variable is empty or unset")
- return 4
- }
- return bltnChdir(ctx, home_path)
- case 1:
- if command.args[0] == "~" {
- home_path := os.Getenv("HOME")
- if len(home_path) == 0 {
- fmt.Fprintln(ctx.stderr, "error: $HOME environment variable is empty or unset")
- return 4
- }
- return bltnChdir(ctx, home_path)
- }
- return bltnChdir(ctx, command.args[0])
- default:
- fmt.Fprintln(ctx.stderr, "usage: cd PATH")
- return 2
- }
-
- case CommandTypeBuiltinPwd:
- if len(command.args) != 0 {
- fmt.Fprintln(ctx.stderr, "usage: pwd")
- return 2
- }
- cwd_path, err := os.Getwd()
- if err != nil {
- panic("error getting current working directory")
- }
- fmt.Fprintln(ctx.stdout, cwd_path)
- return 0
-
- case CommandTypeBuiltinType:
- if len(command.args) != 1 {
- fmt.Fprintln(ctx.stderr, "usage: type COMMAND")
- return 2
- }
- parsed, err := parseCommand(command.args[0])
- if err != nil {
- fmt.Fprintln(ctx.stderr, err)
- return 0
- }
- switch parsed.command_type {
- case CommandTypeNone:
- panic("impossible parseCommand() result")
- case CommandTypeExternal:
- fmt.Fprintf(ctx.stdout, "%s is %s\n", parsed.name, parsed.exec_path)
- default:
- fmt.Fprintf(ctx.stdout, "%s is a shell builtin\n", parsed.name)
- }
- return 0
-
- case CommandTypeExternal:
- cmd := exec.Command(command.exec_path, command.args...)
- cmd.Args[0] = command.name
- cmd.Stdout = ctx.stdout
- cmd.Stderr = ctx.stderr
- cmd.Stdin = ctx.stdin
- err := cmd.Run()
- if err != nil {
- if exiterr, ok := err.(*exec.ExitError); ok {
- return ExitStatus(exiterr.ExitCode())
- // fmt.Printf("handleCommand():exiterr=%#v\n", exiterr.ExitCode())
- } else {
- fmt.Printf("handleCommand():err=%#v\n", err)
- return ExitStatus(65535)
- }
- }
- return 0
-
- default:
- panic("unknown command type")
- }
- return ExitStatus(65535)
- }
-
- func main() {
- ctx := makeContext()
- for {
- command, err := getCommand(&ctx)
- if err != nil {
- fmt.Fprintln(os.Stderr, "Error reading input: ", err)
- os.Exit(1)
- }
- _ = handleCommand(ctx, command)
- }
- }
|