github.com/hattya/go.sh@v0.0.0-20240328132134-f53276d95cc6/interp/interp.go (about)

     1  //
     2  // go.sh/interp :: interp.go
     3  //
     4  //   Copyright (c) 2021 Akinori Hattori <hattya@gmail.com>
     5  //
     6  //   SPDX-License-Identifier: MIT
     7  //
     8  
     9  // Package interp implements an interpreter for the Shell Command Language
    10  // (POSIX.1-2017).
    11  package interp
    12  
    13  import (
    14  	"os"
    15  	"strconv"
    16  	"strings"
    17  )
    18  
    19  const IFS = " \t\n"
    20  
    21  // ExecEnv represents a shell execution environment.
    22  type ExecEnv struct {
    23  	Args    []string
    24  	Opts    Option
    25  	Aliases map[string]string
    26  
    27  	vars map[string]Var
    28  }
    29  
    30  // NewExecEnv returns a new ExecEnv.
    31  func NewExecEnv(name string, args ...string) *ExecEnv {
    32  	env := &ExecEnv{
    33  		Args:    append([]string{name}, args...),
    34  		Aliases: make(map[string]string),
    35  		vars:    make(map[string]Var),
    36  	}
    37  	for _, s := range os.Environ() {
    38  		if i := strings.IndexByte(s[1:], '='); i != -1 {
    39  			env.vars[env.keyFor(s[:i+1])] = Var{
    40  				Name:   s[:i+1],
    41  				Value:  s[i+2:],
    42  				Export: true,
    43  			}
    44  		}
    45  	}
    46  	// shell variables
    47  	env.vars[env.keyFor("IFS")] = Var{
    48  		Name:   "IFS",
    49  		Value:  IFS,
    50  		Export: true,
    51  	}
    52  	return env
    53  }
    54  
    55  // Get retrieves the variable named by the name.
    56  func (env *ExecEnv) Get(name string) (v Var, set bool) {
    57  	if len(name) == 1 {
    58  		var value string
    59  		switch name {
    60  		case "#":
    61  			value = strconv.Itoa(len(env.Args) - 1)
    62  		case "?":
    63  			value = "0"
    64  		case "-":
    65  			value = env.Opts.String()
    66  		case "$":
    67  			value = strconv.Itoa(os.Getpid())
    68  		case "!":
    69  		case "0":
    70  			value = env.Args[0]
    71  		default:
    72  			goto Default
    73  		}
    74  		v = Var{
    75  			Name:  name,
    76  			Value: value,
    77  		}
    78  		set = value != ""
    79  		return
    80  	}
    81  Default:
    82  	if env.isPosParam(name) {
    83  		if i, _ := strconv.Atoi(name); i < len(env.Args) {
    84  			v = Var{
    85  				Name:  strconv.Itoa(i),
    86  				Value: env.Args[i],
    87  			}
    88  			set = true
    89  		}
    90  	} else {
    91  		v, set = env.vars[env.keyFor(name)]
    92  	}
    93  	return
    94  }
    95  
    96  // Set sets the value of the variable named by the name.
    97  func (env *ExecEnv) Set(name, value string) {
    98  	if env.isSpParam(name) || env.isPosParam(name) {
    99  		return
   100  	}
   101  	env.vars[env.keyFor(name)] = Var{
   102  		Name:  name,
   103  		Value: value,
   104  	}
   105  }
   106  
   107  // Unset unsets the variable named by the name.
   108  func (env *ExecEnv) Unset(name string) {
   109  	delete(env.vars, env.keyFor(name))
   110  }
   111  
   112  // Walk walks the variables, calling fn for each.
   113  func (env *ExecEnv) Walk(fn func(Var)) {
   114  	for _, v := range env.vars {
   115  		fn(v)
   116  	}
   117  }
   118  
   119  // isSpParam reports whether s matches the name of a special parameter.
   120  func (env *ExecEnv) isSpParam(s string) bool {
   121  	switch s {
   122  	case "@", "*", "#", "?", "-", "$", "!", "0":
   123  		return true
   124  	}
   125  	return false
   126  }
   127  
   128  // isPosParam reports whether s matches the name of a positional
   129  // parameter.
   130  func (env *ExecEnv) isPosParam(s string) bool {
   131  	for _, r := range s {
   132  		if r < '0' || '9' < r {
   133  			return false
   134  		}
   135  	}
   136  	return s != "" && s != "0"
   137  }
   138  
   139  // Option represents a shell option.
   140  type Option uint
   141  
   142  const (
   143  	AllExport Option = 1 << iota
   144  	ErrExit
   145  	IgnoreEOF
   146  	Monitor
   147  	NoClobber
   148  	NoGlob
   149  	NoExec
   150  	NoLog
   151  	Notify
   152  	NoUnset
   153  	Verbose
   154  	Vi
   155  	XTrace
   156  )
   157  
   158  const optionString = "ae mCfn buv x"
   159  
   160  func (o Option) String() string {
   161  	var b strings.Builder
   162  	for i := 0; i <= len(optionString); i++ {
   163  		switch x := Option(1 << i); x {
   164  		case IgnoreEOF, NoLog, Vi:
   165  		default:
   166  			if o&x != 0 {
   167  				b.WriteByte(optionString[i])
   168  			}
   169  		}
   170  	}
   171  	return b.String()
   172  }
   173  
   174  // Var represents a variable.
   175  type Var struct {
   176  	Name  string
   177  	Value string
   178  
   179  	Export   bool
   180  	ReadOnly bool
   181  }