github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/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 // Package forth implements Forth parsing, which allows 6 // programs to use forth-like syntax to manipulate a stack 7 // of Cells. 8 // It is designed for use by programs 9 // needing to evaluate command-line arguments or simple 10 // expressions to set program variables. It is designed 11 // to map host names to numbers. We use it to 12 // easily convert host names and IP addresses into 13 // parameters. 14 // The language 15 // is a Forth-like postfix notation. Elements are 16 // either commands or strings. Strings are 17 // immediately pushed. Commands consume stack variables 18 // and produce new ones. 19 // Simple examples: 20 // push hostname, strip alpha characters to produce a number. If your 21 // hostname is sb47, top of stack will be left with 47. 22 // hostname hostbase 23 // Get the hostbase, if it is 0 mod 20, return the hostbase / 20, 24 // else return hostbase mod 20 25 // 26 // hostname hostbase dup 20 / swap 20 % dup ifelse 27 // 28 // At the end of the evaluation the stack should have one element 29 // left; that element is popped and returned. It is an error (currently) 30 // to return with a non-empty stack. 31 // This package was used for real work at Sandia National Labs from 2010 to 2012 and possibly later. 32 // Some of the use of error may seem a bit weird but the creation of this package predates the 33 // creation of the error type (it was still an os thing back then). 34 package forth 35 36 import ( 37 "errors" 38 "fmt" 39 "os" 40 "runtime" 41 "strconv" 42 "strings" 43 "sync" 44 ) 45 46 type ( 47 // Op is an opcode type. It does not return an error value, 48 // instead using panic when parsing issues occur, to ease 49 // the programming annoyance of propagating errors up the 50 // stack (following common Go practice for parsers). 51 // If you write an op it can use panic as well. 52 // Lest you get upset about the use of panic, be aware 53 // I've talked to the folks in Go core about this and 54 // they feel it's fine for parsers. 55 Op func(f Forth) 56 // Cell is a stack element. 57 Cell interface{} 58 59 stack struct { 60 stack []Cell 61 } 62 ) 63 64 var ( 65 // Debug is an empty function that can be set to, e.g., 66 // fmt.Printf or log.Printf for debugging. 67 Debug = func(string, ...interface{}) {} 68 opmap map[string]Op 69 mapLock sync.Mutex 70 ) 71 72 func init() { 73 opmap = map[string]Op{ 74 "+": plus, 75 "-": sub, 76 "*": times, 77 "/": div, 78 "%": mod, 79 "swap": swap, 80 "ifelse": ifelse, 81 "hostname": hostname, 82 "hostbase": hostbase, 83 "strcat": strcat, 84 "roundup": roundup, 85 "dup": dup, 86 "drop": drop, 87 "newword": newword, 88 "words": words, 89 } 90 } 91 92 // Forth is an interface used by the package. The interface 93 // requires definition of Push, Pop, Length, Empty (convenience function 94 // meaning Length is 0), Newop (insert a new or replacement operator), 95 // and Reset (clear the stack, mainly diagnostic) 96 type Forth interface { 97 Push(Cell) 98 Pop() Cell 99 Length() int 100 Empty() bool 101 Reset() 102 Stack() []Cell 103 } 104 105 // New creates a new stack 106 func New() Forth { 107 f := new(stack) 108 return f 109 } 110 111 // Getop gets an op from the map. 112 func Getop(n string) Op { 113 mapLock.Lock() 114 defer mapLock.Unlock() 115 op, ok := opmap[n] 116 if !ok { 117 return nil 118 } 119 return op 120 } 121 122 // Putop creates a new operation. We considered having 123 // an opmap per stack but don't feel the package requires it 124 func Putop(n string, op Op) { 125 mapLock.Lock() 126 defer mapLock.Unlock() 127 if _, ok := opmap[n]; ok { 128 panic("Putting %s: op already assigned") 129 } 130 opmap[n] = op 131 } 132 133 // Ops returns the operator map. 134 func Ops() map[string]Op { 135 return opmap 136 } 137 138 // Reset resets the stack to empty 139 func (f *stack) Reset() { 140 f.stack = f.stack[0:0] 141 } 142 143 // Return the stack 144 func (f *stack) Stack() []Cell { 145 return f.stack 146 } 147 148 // Push pushes the interface{} on the stack. 149 func (f *stack) Push(c Cell) { 150 f.stack = append(f.stack, c) 151 Debug("push: %v: stack: %v\n", c, f.stack) 152 } 153 154 // Pop pops the stack. If the stack is Empty Pop will panic. 155 // Eval recovers() the panic. 156 func (f *stack) Pop() (ret Cell) { 157 if len(f.stack) < 1 { 158 panic(errors.New("Empty stack")) 159 } 160 ret = f.stack[len(f.stack)-1] 161 f.stack = f.stack[0 : len(f.stack)-1] 162 Debug("Pop: %v stack %v\n", ret, f.stack) 163 return ret 164 } 165 166 // Length returns the stack length. 167 func (f *stack) Length() int { 168 return len(f.stack) 169 } 170 171 // Empty is a convenience function synonymous with Length == 0 172 func (f *stack) Empty() bool { 173 return len(f.stack) == 0 174 } 175 176 // errRecover converts panics to errstr iff it is an os.Error, panics 177 // otherwise. 178 func errRecover(errp *error) { 179 e := recover() 180 if e != nil { 181 if _, ok := e.(runtime.Error); ok { 182 Debug("errRecover panics with a runtime error") 183 panic(e) 184 } 185 Debug("errRecover returns %v", e) 186 *errp = fmt.Errorf("%v", e) 187 } 188 } 189 190 // Eval takes a Forth and []Cell, pushing each element on the stack or invoking the 191 // operator if it is found in the opmap. 192 func eval(f Forth, cells ...Cell) { 193 Debug("eval cells %v", cells) 194 for _, c := range cells { 195 Debug("eval %v(%T) stack %v", c, c, f.Stack()) 196 switch s := c.(type) { 197 case string: 198 fun := Getop(s) 199 if fun != nil { 200 Debug("eval ... %v:", f.Stack()) 201 fun(f) 202 Debug("eval: Stack now %v", f.Stack()) 203 break 204 } 205 if s[0] == '\'' { 206 s = s[1:] 207 } 208 f.Push(s) 209 Debug("push %v(%T), stack %v", s, s, f.Stack()) 210 default: 211 Debug("push %v(%T), stack %v", s, s, f.Stack()) 212 f.Push(s) 213 } 214 } 215 } 216 217 // Eval calls eval and catches panics. 218 func Eval(f Forth, cells ...Cell) (err error) { 219 defer errRecover(&err) 220 eval(f, cells...) 221 return 222 } 223 224 // EvalString takes a Forth and string and splits the string on space 225 // characters, calling Eval for each one. 226 func EvalString(f Forth, s string) (err error) { 227 for _, c := range strings.Fields(s) { 228 if err = Eval(f, c); err != nil { 229 return 230 } 231 } 232 Debug("EvalString err %v", err) 233 return 234 } 235 236 // EvalPop takes a Forth and string, calls EvalString, and 237 // returns TOS and an error, if any. 238 // For EvalPop it is an error to leave the stack non-Empty. 239 // EvalPop is typically used for programs that want to 240 // parse forth contained in, e.g., flag.Args(), and return 241 // a result. In most use cases, we want the stack to be empty. 242 func EvalPop(f Forth, s string) (ret Cell, err error) { 243 defer errRecover(&err) 244 if err = EvalString(f, s); err != nil { 245 return 246 } 247 if f.Length() != 1 { 248 panic(fmt.Sprintf("%v: length is not 1", f.Stack())) 249 } 250 ret = f.Pop() 251 Debug("EvalPop ret %v err %v", ret, err) 252 return 253 } 254 255 // String returns the Top Of Stack if it is a string 256 // or panics. 257 func String(f Forth) string { 258 c := f.Pop() 259 switch s := c.(type) { 260 case string: 261 return s 262 default: 263 panic(fmt.Sprintf("Can't convert %v to a string", c)) 264 } 265 } 266 267 // toInt converts to int64. 268 func toInt(f Forth) int64 { 269 Debug("toint %v", f.Stack()) 270 c := f.Pop() 271 Debug("%T", c) 272 switch s := c.(type) { 273 case string: 274 i, err := strconv.ParseInt(s, 0, 64) 275 if err != nil { 276 panic(err) 277 } 278 return i 279 case int64: 280 return s 281 default: 282 panic(fmt.Sprintf("NaN: %T", c)) 283 } 284 } 285 286 func plus(f Forth) { 287 x := toInt(f) 288 y := toInt(f) 289 z := x + y 290 f.Push(strconv.FormatInt(z, 10)) 291 } 292 293 func words(f Forth) { 294 mapLock.Lock() 295 defer mapLock.Unlock() 296 var w []string 297 for i := range opmap { 298 w = append(w, i) 299 } 300 f.Push(w) 301 } 302 303 func newword(f Forth) { 304 s := String(f) 305 n := toInt(f) 306 // Pop <n> Cells. 307 if f.Length() < int(n) { 308 panic(fmt.Sprintf("newword %s: stack is %d elements, need %d", s, f.Length(), n)) 309 } 310 var c = make([]Cell, n) 311 for i := n; i > 0; i-- { 312 c[i-1] = f.Pop() 313 } 314 Putop(s, func(f Forth) { 315 Debug("c %v", c) 316 eval(f, c...) 317 }) 318 } 319 320 func drop(f Forth) { 321 _ = f.Pop() 322 } 323 324 func times(f Forth) { 325 x := toInt(f) 326 y := toInt(f) 327 z := x * y 328 f.Push(strconv.FormatInt(z, 10)) 329 } 330 331 func sub(f Forth) { 332 x := toInt(f) 333 y := toInt(f) 334 z := y - x 335 f.Push(strconv.FormatInt(z, 10)) 336 } 337 338 func div(f Forth) { 339 x := toInt(f) 340 y := toInt(f) 341 z := y / x 342 f.Push(strconv.FormatInt(z, 10)) 343 } 344 345 func mod(f Forth) { 346 x := toInt(f) 347 y := toInt(f) 348 z := y % x 349 f.Push(strconv.FormatInt(z, 10)) 350 } 351 352 func roundup(f Forth) { 353 rnd := toInt(f) 354 v := toInt(f) 355 v = ((v + rnd - 1) / rnd) * rnd 356 f.Push(strconv.FormatInt(v, 10)) 357 } 358 359 func swap(f Forth) { 360 x := f.Pop() 361 y := f.Pop() 362 f.Push(x) 363 f.Push(y) 364 } 365 366 func strcat(f Forth) { 367 x := String(f) 368 y := String(f) 369 f.Push(y + x) 370 } 371 372 func dup(f Forth) { 373 x := f.Pop() 374 f.Push(x) 375 f.Push(x) 376 } 377 378 func ifelse(f Forth) { 379 x := toInt(f) 380 y := f.Pop() 381 z := f.Pop() 382 if x != 0 { 383 f.Push(y) 384 } else { 385 f.Push(z) 386 } 387 } 388 389 func hostname(f Forth) { 390 h, err := os.Hostname() 391 if err != nil { 392 panic("No hostname") 393 } 394 f.Push(h) 395 } 396 397 func hostbase(f Forth) { 398 host := String(f) 399 f.Push(strings.TrimLeft(host, "abcdefghijklmnopqrstuvwxyz -")) 400 } 401 402 // NewWord allows for definition of new operators from strings. 403 // For example, should you wish to create a word which adds a number 404 // to itself twice, you can call: 405 // NewWord(f, "d3d", "dup dup + +") 406 // which does two dups, and two adds. 407 func NewWord(f Forth, name string, cell Cell, cells ...Cell) { 408 cmd := append([]Cell{cell}, cells...) 409 newword := func(f Forth) { 410 eval(f, cmd...) 411 } 412 Putop(name, newword) 413 }