github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/pkg/forth/forth.go (about) 1 // Copyright 2010-2018 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 forth package is designed for use by programs 6 // needing to evaluate command-line arguments or simple 7 // expressions to set program variables. It is designed 8 // to map host names to numbers. We use it to 9 // easily convert host names and IP addresses into 10 // parameters. 11 // The language 12 // is a Forth-like postfix notation. Elements are 13 // either commands or strings. Strings are 14 // immediately pushed. Commands consume stack variables 15 // and produce new ones. 16 // Simple examples: 17 // push hostname, strip alpha characters to produce a number. If your 18 // hostname is sb47, top of stack will be left with 47. 19 // hostname hostbase 20 // Get the hostbase, if it is 0 mod 20, return the hostbase / 20, 21 // else return hostbase mod 20 22 // 23 // hostname hostbase dup 20 / swap 20 % dup ifelse 24 // 25 // At the end of the evaluation the stack should have one element 26 // left; that element is popped and returned. It is an error (currently) 27 // to return with a non-empty stack. 28 // This package was used for real work at Sandia National Labs from 2010 to 2012 and possibly later. 29 // Some of the use of error may seem a bit weird but the creation of this package predates the 30 // creation of the error type (it was still an os thing back then). 31 package forth 32 33 import ( 34 "errors" 35 "os" 36 "runtime" 37 "strconv" 38 "strings" 39 ) 40 41 type ForthOp func(f Forth) 42 43 type forthstack struct { 44 stack []string 45 } 46 47 var opmap = map[string]ForthOp{ 48 "+": plus, 49 "-": sub, 50 "*": times, 51 "/": div, 52 "%": mod, 53 "swap": swap, 54 "ifelse": ifelse, 55 "hostname": hostname, 56 "hostbase": hostbase, 57 "strcat": strcat, 58 "roundup": roundup, 59 "dup": dup, 60 } 61 62 // Forth is an interface used by the package. The interface 63 // requires definition of Push, Pop, Length, Empty (convenience function 64 // meaning Length is 0), Newop (insert a new or replacement operator), 65 // and Reset (clear the stack, mainly diagnostic) 66 type Forth interface { 67 Push(string) 68 Pop() string 69 Length() int 70 Empty() bool 71 Newop(string, ForthOp) 72 Reset() 73 Stack() []string 74 } 75 76 // New creates a new stack 77 func New() Forth { 78 f := new(forthstack) 79 return f 80 } 81 82 // Newop creates a new operation. We considered having 83 // an opmap per stack but don't feel the package requires it 84 func (f *forthstack) Newop(n string, op ForthOp) { 85 opmap[n] = op 86 } 87 88 func Ops() map[string]ForthOp { 89 return opmap 90 } 91 92 // Reset resets the stack to empty 93 func (f *forthstack) Reset() { 94 f.stack = f.stack[0:0] 95 } 96 97 // Return the stack as a []string 98 func (f *forthstack) Stack() []string { 99 return f.stack 100 } 101 102 // Push pushes the string on the stack. 103 func (f *forthstack) Push(s string) { 104 f.stack = append(f.stack, s) 105 //fmt.Printf("push: %v: stack: %v\n", s, f.stack) 106 } 107 108 // Pop pops the stack. If the stack is Empty Pop will panic. 109 // Eval recovers() the panic. 110 func (f *forthstack) Pop() (ret string) { 111 112 if len(f.stack) < 1 { 113 panic(errors.New("Empty stack")) 114 } 115 ret = f.stack[len(f.stack)-1] 116 f.stack = f.stack[0 : len(f.stack)-1] 117 //fmt.Printf("Pop: %v stack %v\n", ret, f.stack) 118 return ret 119 } 120 121 // Length returns the stack length. 122 func (f *forthstack) Length() int { 123 return len(f.stack) 124 } 125 126 // Empty is a convenience function synonymous with Length == 0 127 func (f *forthstack) Empty() bool { 128 return len(f.stack) == 0 129 } 130 131 // errRecover converts panics to errstr iff it is an os.Error, panics 132 // otherwise. 133 func errRecover(errp *error) { 134 e := recover() 135 if e != nil { 136 if _, ok := e.(runtime.Error); ok { 137 panic(e) 138 } 139 *errp = e.(error) 140 } 141 } 142 143 /* iEval takes a Forth and strings and splits the string on space 144 * characters, pushing each element on the stack or invoking the 145 * operator if it is found in the opmap. 146 */ 147 func iEval(f Forth, s string) { 148 // TODO: create a separator list based on isspace and all the 149 // non alpha numberic characters in the opmap. 150 for _, val := range strings.Fields(s) { 151 //fmt.Printf("eval %s stack %v", val, f.Stack()) 152 fun := opmap[val] 153 if fun != nil { 154 //fmt.Printf("Eval ...:") 155 fun(f) 156 //fmt.Printf("Stack now %v", f.Stack()) 157 } else { 158 f.Push(val) 159 //fmt.Printf("push %s, stack %v", val, f.Stack()) 160 } 161 } 162 return 163 } 164 165 /* Eval takes a Forth and strings and splits the string on space 166 * characters, pushing each element on the stack or invoking the 167 * operator if it is found in the opmap. It returns TOS when it is done. 168 * it is an error to leave the stack non-Empty. 169 */ 170 func Eval(f Forth, s string) (ret string, err error) { 171 defer errRecover(&err) 172 iEval(f, s) 173 ret = f.Pop() 174 return 175 176 } 177 178 // toInt converts to int64. 179 func toInt(f Forth) int64 { 180 i, err := strconv.ParseInt(f.Pop(), 0, 64) 181 if err != nil { 182 panic(err) 183 } 184 return i 185 } 186 187 func plus(f Forth) { 188 x := toInt(f) 189 y := toInt(f) 190 z := x + y 191 f.Push(strconv.FormatInt(z, 10)) 192 } 193 194 func times(f Forth) { 195 x := toInt(f) 196 y := toInt(f) 197 z := x * y 198 f.Push(strconv.FormatInt(z, 10)) 199 } 200 201 func sub(f Forth) { 202 x := toInt(f) 203 y := toInt(f) 204 z := y - x 205 f.Push(strconv.FormatInt(z, 10)) 206 } 207 208 func div(f Forth) { 209 x := toInt(f) 210 y := toInt(f) 211 z := y / x 212 f.Push(strconv.FormatInt(z, 10)) 213 } 214 215 func mod(f Forth) { 216 x := toInt(f) 217 y := toInt(f) 218 z := y % x 219 f.Push(strconv.FormatInt(z, 10)) 220 } 221 222 func roundup(f Forth) { 223 rnd := toInt(f) 224 v := toInt(f) 225 v = ((v + rnd - 1) / rnd) * rnd 226 f.Push(strconv.FormatInt(v, 10)) 227 } 228 229 func swap(f Forth) { 230 x := f.Pop() 231 y := f.Pop() 232 f.Push(x) 233 f.Push(y) 234 } 235 236 func strcat(f Forth) { 237 x := f.Pop() 238 y := f.Pop() 239 f.Push(y + x) 240 } 241 242 func dup(f Forth) { 243 x := f.Pop() 244 f.Push(x) 245 f.Push(x) 246 } 247 248 func ifelse(f Forth) { 249 x := toInt(f) 250 y := f.Pop() 251 z := f.Pop() 252 if x != 0 { 253 f.Push(y) 254 } else { 255 f.Push(z) 256 } 257 } 258 259 func hostname(f Forth) { 260 h, err := os.Hostname() 261 if err != nil { 262 panic("No hostname") 263 } 264 f.Push(h) 265 } 266 267 func hostbase(f Forth) { 268 host := f.Pop() 269 f.Push(strings.TrimLeft(host, "abcdefghijklmnopqrstuvwxyz -")) 270 } 271 272 func NewWord(f Forth, name, command string) { 273 newword := func(f Forth) { 274 iEval(f, command) 275 return 276 } 277 opmap[name] = newword 278 return 279 }