gopkg.in/hugelgupf/u-root.v2@v2.0.0-20180831055005-3f8fdb0ce09d/cmds/rush/parse.go (about)

     1  // Copyright 2014-2017 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // The u-root shell is intended to be very simple, since builtins and extensions
     6  // are written in Go. It should not need YACC. As in the JSON parser, we hope this
     7  // simple state machine will do the job.
     8  package main
     9  
    10  import (
    11  	"bufio"
    12  	"bytes"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"io/ioutil"
    17  	"os/exec"
    18  	"path"
    19  	"path/filepath"
    20  	"strings"
    21  )
    22  
    23  type arg struct {
    24  	val string
    25  	mod string
    26  }
    27  
    28  // The Command struct is initially filled in by the parser. The shell itself
    29  // adds to it as processing continues, and then uses it to creates os.Commands
    30  type Command struct {
    31  	*exec.Cmd
    32  	// These are filled in by the parser.
    33  	args  []arg
    34  	fdmap map[int]string
    35  	files map[int]io.Closer
    36  	link  string
    37  	bg    bool
    38  
    39  	// These are set up by the shell as it evaluates the Commands
    40  	// provided by the parser.
    41  	// we separate the command so people don't have to put checks for the length
    42  	// of argv in their builtins. We do that for them.
    43  	cmd  string
    44  	argv []string
    45  }
    46  
    47  var (
    48  	cmds  []Command
    49  	punct = "<>|&$ \t\n"
    50  )
    51  
    52  func pushback(b *bufio.Reader) {
    53  	err := b.UnreadByte()
    54  	if err != nil {
    55  		panic(fmt.Errorf("unreading bufio: %v", err))
    56  	}
    57  }
    58  
    59  func one(b *bufio.Reader) byte {
    60  	c, err := b.ReadByte()
    61  	//fmt.Printf("one '%v' %v\n", c, err)
    62  	if err == io.EOF {
    63  		return 0
    64  	}
    65  	if err != nil {
    66  		panic(fmt.Errorf("reading bufio: %v", err))
    67  	}
    68  	return c
    69  }
    70  
    71  func next(b *bufio.Reader) byte {
    72  	c := one(b)
    73  	if c == '\\' {
    74  		return one(b)
    75  	}
    76  	return byte(c)
    77  }
    78  
    79  // Tokenize stuff coming in from the stream. For everything but an arg, the
    80  // type is just the thing itself, since we can switch on strings.
    81  func tok(b *bufio.Reader) (string, string) {
    82  	var tokType, arg string
    83  	c := next(b)
    84  
    85  	//fmt.Printf("TOK %v", c)
    86  	switch c {
    87  	case 0:
    88  		return "EOF", ""
    89  	case '>':
    90  		return "FD", "1"
    91  	case '<':
    92  		return "FD", "0"
    93  	// yes, I realize $ handling is still pretty hokey.
    94  	case '$':
    95  		arg = ""
    96  		c = next(b)
    97  		for {
    98  			if c == 0 {
    99  				break
   100  			}
   101  			if strings.Index(punct, string(c)) > -1 {
   102  				pushback(b)
   103  				break
   104  			}
   105  			arg = arg + string(c)
   106  			c = next(b)
   107  		}
   108  		return "ENV", arg
   109  	case '\'':
   110  		for {
   111  			nc := next(b)
   112  			if nc == '\'' {
   113  				return "ARG", arg
   114  			}
   115  			arg = arg + string(nc)
   116  		}
   117  	case ' ', '\t':
   118  		return "white", string(c)
   119  	case '\n':
   120  		//fmt.Printf("NEWLINE\n")
   121  		return "EOL", ""
   122  	case '|', '&':
   123  		//fmt.Printf("LINK %v\n", c)
   124  		// peek ahead. We need the literal, so don't use next()
   125  		nc := one(b)
   126  		if nc == c {
   127  			//fmt.Printf("LINK %v\n", string(c)+string(c))
   128  			return "LINK", string(c) + string(c)
   129  		}
   130  		pushback(b)
   131  		if c == '&' {
   132  			//fmt.Printf("BG\n")
   133  			tokType = "BG"
   134  			if nc == 0 {
   135  				tokType = "EOL"
   136  			}
   137  			return "BG", tokType
   138  		}
   139  		//fmt.Printf("LINK %v\n", string(c))
   140  		return "LINK", string(c)
   141  	default:
   142  		for {
   143  			if c == 0 {
   144  				return "ARG", arg
   145  			}
   146  			if strings.Index(punct, string(c)) > -1 {
   147  				pushback(b)
   148  				return "ARG", arg
   149  			}
   150  			arg = arg + string(c)
   151  			c = next(b)
   152  		}
   153  
   154  	}
   155  
   156  }
   157  
   158  // get an ARG. It has to work.
   159  func getArg(b *bufio.Reader, what string) string {
   160  	for {
   161  		nt, s := tok(b)
   162  		if nt == "EOF" || nt == "EOL" {
   163  			panic(fmt.Errorf("%v requires an argument", what))
   164  		}
   165  		if nt == "white" {
   166  			continue
   167  		}
   168  		if nt != "ARG" {
   169  			panic(fmt.Errorf("%v requires an argument, not %v", what, nt))
   170  		}
   171  		return s
   172  	}
   173  }
   174  func parsestring(b *bufio.Reader, c *Command) (*Command, string) {
   175  	t, s := tok(b)
   176  	if s == "\n" || t == "EOF" || t == "EOL" {
   177  		return nil, t
   178  	}
   179  	for {
   180  		switch t {
   181  		case "ENV":
   182  			if !path.IsAbs(s) {
   183  				s = filepath.Join(envDir, s)
   184  			}
   185  			b, err := ioutil.ReadFile(s)
   186  			if err != nil {
   187  				panic(fmt.Errorf("%s: %v", s, err))
   188  			}
   189  			f := bufio.NewReader(bytes.NewReader(b))
   190  			// the whole string is consumed.
   191  			parsestring(f, c)
   192  		case "ARG":
   193  			c.args = append(c.args, arg{s, t})
   194  		case "white":
   195  		case "FD":
   196  			x := 0
   197  			_, err := fmt.Sscanf(s, "%v", &x)
   198  			if err != nil {
   199  				panic(fmt.Errorf("bad FD on redirect: %v, %v", s, err))
   200  			}
   201  			// whitespace is allowed
   202  			c.fdmap[x] = getArg(b, t)
   203  		// LINK and BG are similar save that LINK requires another command. If we don't get one, well.
   204  		case "LINK":
   205  			c.link = s
   206  			//fmt.Printf("LINK %v %v\n", c, s)
   207  			return c, t
   208  		case "BG":
   209  			c.bg = true
   210  			return c, t
   211  		case "EOF":
   212  			return c, t
   213  		case "EOL":
   214  			return c, t
   215  		default:
   216  			panic(fmt.Errorf("unknown token type %v", t))
   217  		}
   218  		t, s = tok(b)
   219  	}
   220  }
   221  func parse(b *bufio.Reader) (*Command, string) {
   222  	//fmt.Printf("%v %v\n", t, s)
   223  	c := newCommand()
   224  	return parsestring(b, c)
   225  }
   226  
   227  func newCommand() *Command {
   228  	return &Command{fdmap: make(map[int]string), files: make(map[int]io.Closer)}
   229  }
   230  
   231  // Just eat it up until you have all the commands you need.
   232  func parsecommands(b *bufio.Reader) ([]*Command, string) {
   233  	cmds := make([]*Command, 0)
   234  	for {
   235  		c, t := parse(b)
   236  		if c == nil {
   237  			return cmds, t
   238  		}
   239  		//fmt.Printf("cmd  %v\n", *c)
   240  		cmds = append(cmds, c)
   241  		if t == "EOF" || t == "EOL" {
   242  			return cmds, t
   243  		}
   244  	}
   245  }
   246  
   247  func getCommand(b *bufio.Reader) (c []*Command, t string, err error) {
   248  	defer func() {
   249  		if e := recover(); e != nil {
   250  			err = e.(error)
   251  		}
   252  	}()
   253  
   254  	// TODO: put a recover here that just returns an error.
   255  	c, t = parsecommands(b)
   256  	// the rules.
   257  	// For now, no empty commands.
   258  	// Can't have a redir and a redirect for fd1.
   259  	for i, v := range c {
   260  		if len(v.args) == 0 {
   261  			return nil, "", errors.New("empty commands not allowed (yet)")
   262  		}
   263  		if v.link == "|" && v.fdmap[1] != "" {
   264  			return nil, "", errors.New("Can't have a pipe and > on one command")
   265  		}
   266  		if v.link == "|" && i == len(c)-1 {
   267  			return nil, "", errors.New("Can't have a pipe to nowhere")
   268  		}
   269  		if i < len(c)-1 && v.link == "|" && c[i+1].fdmap[0] != "" {
   270  			return nil, "", errors.New("Can't have a pipe to command with redirect on stdin")
   271  		}
   272  	}
   273  	return c, t, err
   274  }
   275  
   276  /*
   277  func main() {
   278  	b := bufio.NewReader(os.Stdin)
   279  	for {
   280  	    c, t, err := getCommand(b)
   281  		fmt.Printf("%v %v %v\n", c, t, err)
   282  	    if t == "EOF" {
   283  	       break
   284  	       }
   285  	       }
   286  }
   287  */