github.com/rminnich/u-root@v7.0.0+incompatible/pkg/pogosh/shell.go (about)

     1  // Copyright 2020 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  // Package pogosh implements a small POSIX-compatible shell.
     6  package pogosh
     7  
     8  import (
     9  	"fmt"
    10  	"io/ioutil"
    11  )
    12  
    13  // Run executes the given fragment of shell.
    14  //
    15  // There are three circumstances for this function to return:
    16  //
    17  // 1. There is a shell error (compiler error, file not found, ...)
    18  // 2. The script calls the exit builtin.
    19  // 3. The script reaches the end of input.
    20  //
    21  // In the case the shell script execs another process, this function will not
    22  // return (unless the Exec function is appropriately overriden).
    23  func (s *State) Run(script string) (exitCode int, err error) {
    24  	err = isASCII(script)
    25  	if err != nil {
    26  		return 0, err
    27  	}
    28  
    29  	// Lex
    30  	tokens, err := tokenize(script)
    31  	if err != nil {
    32  		return 0, err
    33  	}
    34  
    35  	// Parse
    36  	eof := token{script[len(script):], ttEOF}
    37  	// TODO: append newline instead of EOF, or make EOF the only empty value ""
    38  	tokens = append(tokens, eof) // augment
    39  	t := tokenizer{tokens}
    40  	cmd := parseProgram(s, &t)
    41  	if t.ts[0].ttype != ttEOF {
    42  		panic("expected EOF") // TODO: better error message
    43  	}
    44  
    45  	// Exit code
    46  	defer func() {
    47  		switch r := recover().(type) {
    48  		case nil:
    49  		case exitError:
    50  			exitCode = r.code
    51  		case error:
    52  			exitCode = 1
    53  			err = r
    54  		default:
    55  			panic(r) // TODO: clobbers stack trace
    56  		}
    57  	}()
    58  
    59  	// Execute
    60  	cmd.exec(s)
    61  
    62  	return 0, err
    63  }
    64  
    65  // RunFile is a convenient wrapper around Run.
    66  func (s *State) RunFile(filename string) (int, error) {
    67  	script, err := ioutil.ReadFile(filename)
    68  	if err != nil {
    69  		return 0, err
    70  	}
    71  	return s.Run(string(script))
    72  }
    73  
    74  func isASCII(str string) error {
    75  	lineNumber := 0
    76  	lineStart := 0
    77  	for i, v := range []byte(str) {
    78  		if v == '\n' {
    79  			lineNumber++
    80  			lineStart = i + 1
    81  		} else if v > 127 {
    82  			// TODO: include filename if possible
    83  			return fmt.Errorf("<pogosh>:%v:%v: non-ascii character, '\\x%x'", lineNumber+1, i-lineStart+1, v)
    84  		}
    85  	}
    86  	return nil
    87  }