github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/exp/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 "errors" 13 "fmt" 14 "io" 15 "os/exec" 16 "strings" 17 ) 18 19 type arg struct { 20 val string 21 mod string 22 } 23 24 // The Command struct is initially filled in by the parser. The shell itself 25 // adds to it as processing continues, and then uses it to creates os.Commands 26 type Command struct { 27 *exec.Cmd 28 // These are filled in by the parser. 29 Args []arg 30 fdmap map[int]string 31 files map[int]io.Closer 32 Link string 33 BG bool 34 35 // These are set up by the shell as it evaluates the Commands 36 // provided by the parser. 37 // we separate the command so people don't have to put checks for the length 38 // of argv in their builtins. We do that for them. 39 cmd string 40 argv []string 41 } 42 43 var ( 44 cmds []Command 45 punct = "<>|&$ \t\n" 46 ) 47 48 func pushback(b *bufio.Reader) { 49 // If we can't UnreadByte it will get us an obscure error, but 50 // it is hard to tell what else to do. 51 _ = b.UnreadByte() 52 } 53 54 func one(b *bufio.Reader) byte { 55 c, err := b.ReadByte() 56 // On any kind of error, just return 0. 57 // This is not a serious kind of shell any more, it 58 // is more for diagnostics when things are really broken, 59 // so we need not be too picky about errors. 60 if err != nil { 61 return 0 62 } 63 return c 64 } 65 66 func next(b *bufio.Reader) byte { 67 c := one(b) 68 if c == '\\' { 69 return one(b) 70 } 71 return byte(c) 72 } 73 74 // Tokenize stuff coming in from the stream. For everything but an arg, the 75 // type is just the thing itself, since we can switch on strings. 76 func tok(b *bufio.Reader) (string, string) { 77 var tokType, arg string 78 c := next(b) 79 80 switch c { 81 case 0, 4: 82 return "EOF", "" 83 case '>': 84 return "FD", "1" 85 case '<': 86 return "FD", "0" 87 // yes, I realize $ handling is still pretty hokey. 88 // And, again, this is a diagnostic tool now, not a general 89 // purpose shell, so the hell with it. 90 case '$': 91 arg = "" 92 return "ENV", arg 93 case '\'': 94 for { 95 nc := next(b) 96 if nc == '\'' { 97 return "ARG", arg 98 } 99 arg = arg + string(nc) 100 } 101 case ' ', '\t': 102 return "white", string(c) 103 case '\n', '\r': 104 return "EOL", "" 105 case '|', '&': 106 // peek ahead. We need the literal, so don't use next() 107 nc := one(b) 108 if nc == c { 109 return "LINK", string(c) + string(c) 110 } 111 if nc != 0 { 112 pushback(b) 113 } 114 if c == '&' { 115 tokType = "BG" 116 if nc == 0 { 117 tokType = "EOL" 118 } 119 return "BG", tokType 120 } 121 return "LINK", string(c) 122 default: 123 for { 124 if c == 0 { 125 return "ARG", arg 126 } 127 if strings.Contains(punct, string(c)) { 128 pushback(b) 129 return "ARG", arg 130 } 131 arg = arg + string(c) 132 c = next(b) 133 } 134 135 } 136 } 137 138 // get an ARG. It has to work. 139 func getArg(b *bufio.Reader, what string) string { 140 for { 141 nt, s := tok(b) 142 if nt == "EOF" || nt == "EOL" { 143 // We used to panic here, but what's the sense in that? 144 return "" 145 } 146 if nt == "white" { 147 continue 148 } 149 // It has to work, but if not ... too bad. 150 if nt != "ARG" { 151 return "" 152 } 153 return s 154 } 155 } 156 157 func parsestring(b *bufio.Reader, c *Command) (*Command, string) { 158 t, s := tok(b) 159 if s == "\n" || t == "EOF" || t == "EOL" { 160 return nil, t 161 } 162 for { 163 switch t { 164 // In old rush, env strings were substituted wholesale, and 165 // parsed in line. This was very useful, but nobody uses it so... 166 // for now, screw it. Do environment later. 167 case "ENV": 168 case "ARG": 169 c.Args = append(c.Args, arg{s, t}) 170 case "white": 171 case "FD": 172 x := 0 173 _, err := fmt.Sscanf(s, "%v", &x) 174 if err != nil { 175 panic(fmt.Errorf("bad FD on redirect: %v, %v", s, err)) 176 } 177 // whitespace is allowed 178 c.fdmap[x] = getArg(b, t) 179 // LINK and BG are similar save that LINK requires another command. If we don't get one, well. 180 case "LINK": 181 c.Link = s 182 return c, t 183 case "BG": 184 c.BG = true 185 return c, t 186 case "EOF": 187 return c, t 188 case "EOL": 189 return c, t 190 default: 191 panic(fmt.Errorf("unknown token type %v", t)) 192 } 193 t, s = tok(b) 194 } 195 } 196 197 func parse(b *bufio.Reader) (*Command, string) { 198 c := newCommand() 199 return parsestring(b, c) 200 } 201 202 func newCommand() *Command { 203 return &Command{Cmd: exec.Command(""), fdmap: make(map[int]string), files: make(map[int]io.Closer)} 204 } 205 206 // Just eat it up until you have all the commands you need. 207 func parsecommands(b *bufio.Reader) ([]*Command, string) { 208 cmds := make([]*Command, 0) 209 for { 210 c, t := parse(b) 211 if c == nil { 212 return cmds, t 213 } 214 cmds = append(cmds, c) 215 if t == "EOF" || t == "EOL" { 216 return cmds, t 217 } 218 } 219 } 220 221 func getCommand(b *bufio.Reader) (c []*Command, t string, err error) { 222 defer func() { 223 if e := recover(); e != nil { 224 err = e.(error) 225 } 226 }() 227 228 // TODO: put a recover here that just returns an error. 229 c, t = parsecommands(b) 230 // the rules. 231 // For now, no empty commands. 232 // Can't have a redir and a redirect for fd1. 233 for i, v := range c { 234 if len(v.Args) == 0 { 235 return nil, "", errors.New("empty commands not allowed (yet)") 236 } 237 if v.Link == "|" && v.fdmap[1] != "" { 238 return nil, "", errors.New("Can't have a pipe and > on one command") 239 } 240 if v.Link == "|" && i == len(c)-1 { 241 return nil, "", errors.New("Can't have a pipe to nowhere") 242 } 243 if i < len(c)-1 && v.Link == "|" && c[i+1].fdmap[0] != "" { 244 return nil, "", errors.New("Can't have a pipe to command with redirect on stdin") 245 } 246 } 247 return c, t, err 248 } 249 250 /* 251 func main() { 252 b := bufio.NewReader(os.Stdin) 253 for { 254 c, t, err := getCommand(b) 255 fmt.Printf("%v %v %v\n", c, t, err) 256 if t == "EOF" { 257 break 258 } 259 } 260 } 261 */