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 }