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 */