github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+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 panic(e) 183 } 184 *errp = fmt.Errorf("%v", e) 185 } 186 } 187 188 // Eval takes a Forth and []Cell, pushing each element on the stack or invoking the 189 // operator if it is found in the opmap. 190 func Eval(f Forth, cells ...Cell) (err error) { 191 defer errRecover(&err) 192 for _, c := range cells { 193 Debug("eval %s(%T) stack %v", c, c, f.Stack()) 194 switch s := c.(type) { 195 case string: 196 fun := Getop(s) 197 if fun != nil { 198 Debug("Eval ... %v:", f.Stack()) 199 fun(f) 200 Debug("Stack now %v", f.Stack()) 201 break 202 } 203 if s[0] == '\'' { 204 s = s[1:] 205 } 206 f.Push(s) 207 Debug("push %s, stack %v", s, f.Stack()) 208 default: 209 Debug("push %s, stack %v", s, f.Stack()) 210 f.Push(s) 211 } 212 } 213 return nil 214 } 215 216 // EvalString takes a Forth and string and splits the string on space 217 // characters, calling Eval for each one. 218 func EvalString(f Forth, s string) (err error) { 219 for _, c := range strings.Fields(s) { 220 if err = Eval(f, c); err != nil { 221 Debug("EvalString Eval err: err %v", err) 222 return 223 } 224 } 225 Debug("EvalString err %v", err) 226 return 227 } 228 229 // EvalPop takes a Forth and string, calls EvalString, and 230 // returns TOS and an error, if any. 231 // For EvalPop it is an error to leave the stack non-Empty. 232 // EvalPop is typically used for programs that want to 233 // parse forth contained in, e.g., flag.Args(), and return 234 // a result. In most use cases, we want the stack to be empty. 235 func EvalPop(f Forth, s string) (ret Cell, err error) { 236 defer errRecover(&err) 237 if err = EvalString(f, s); err != nil { 238 return 239 } 240 if f.Length() != 1 { 241 panic(fmt.Sprintf("%v: length is not 1", f.Stack())) 242 } 243 ret = f.Pop() 244 Debug("EvalPop ret %v err %v", ret, err) 245 return 246 } 247 248 // String returns the Top Of Stack if it is a string 249 // or panics. 250 func String(f Forth) string { 251 c := f.Pop() 252 switch s := c.(type) { 253 case string: 254 return s 255 default: 256 panic(fmt.Sprintf("Can't convert %v to a string", c)) 257 } 258 } 259 260 // toInt converts to int64. 261 func toInt(f Forth) int64 { 262 Debug("toint %v", f.Stack()) 263 c := f.Pop() 264 Debug("%T", c) 265 switch s := c.(type) { 266 case string: 267 i, err := strconv.ParseInt(s, 0, 64) 268 if err != nil { 269 panic(err) 270 } 271 return i 272 case int64: 273 return s 274 default: 275 panic(fmt.Sprintf("NaN: %T", c)) 276 } 277 } 278 279 func plus(f Forth) { 280 Debug("plus") 281 x := toInt(f) 282 y := toInt(f) 283 Debug("Plus %v %v", x, y) 284 z := x + y 285 f.Push(strconv.FormatInt(z, 10)) 286 } 287 288 func words(f Forth) { 289 mapLock.Lock() 290 defer mapLock.Unlock() 291 var w []string 292 for i := range opmap { 293 w = append(w, i) 294 } 295 f.Push(w) 296 } 297 298 func newword(f Forth) { 299 s := String(f) 300 n := toInt(f) 301 // Pop <n> Cells. 302 if f.Length() < int(n) { 303 panic(fmt.Sprintf("newword %s: stack is %d elements, need %d", s, f.Length(), n)) 304 } 305 var c = make([]Cell, n) 306 for i := range c { 307 c[i] = f.Pop() 308 } 309 Putop(s, func(f Forth) { 310 Eval(f, c...) 311 }) 312 } 313 314 func drop(f Forth) { 315 _ = f.Pop() 316 } 317 318 func times(f Forth) { 319 x := toInt(f) 320 y := toInt(f) 321 z := x * y 322 f.Push(strconv.FormatInt(z, 10)) 323 } 324 325 func sub(f Forth) { 326 x := toInt(f) 327 y := toInt(f) 328 z := y - x 329 f.Push(strconv.FormatInt(z, 10)) 330 } 331 332 func div(f Forth) { 333 x := toInt(f) 334 y := toInt(f) 335 z := y / x 336 f.Push(strconv.FormatInt(z, 10)) 337 } 338 339 func mod(f Forth) { 340 x := toInt(f) 341 y := toInt(f) 342 z := y % x 343 f.Push(strconv.FormatInt(z, 10)) 344 } 345 346 func roundup(f Forth) { 347 rnd := toInt(f) 348 v := toInt(f) 349 v = ((v + rnd - 1) / rnd) * rnd 350 f.Push(strconv.FormatInt(v, 10)) 351 } 352 353 func swap(f Forth) { 354 x := f.Pop() 355 y := f.Pop() 356 f.Push(x) 357 f.Push(y) 358 } 359 360 func strcat(f Forth) { 361 x := String(f) 362 y := String(f) 363 f.Push(y + x) 364 } 365 366 func dup(f Forth) { 367 x := f.Pop() 368 f.Push(x) 369 f.Push(x) 370 } 371 372 func ifelse(f Forth) { 373 x := toInt(f) 374 y := f.Pop() 375 z := f.Pop() 376 if x != 0 { 377 f.Push(y) 378 } else { 379 f.Push(z) 380 } 381 } 382 383 func hostname(f Forth) { 384 h, err := os.Hostname() 385 if err != nil { 386 panic("No hostname") 387 } 388 f.Push(h) 389 } 390 391 func hostbase(f Forth) { 392 host := String(f) 393 f.Push(strings.TrimLeft(host, "abcdefghijklmnopqrstuvwxyz -")) 394 } 395 396 // NewWord allows for definition of new operators from strings. 397 // For example, should you wish to create a word which adds a number 398 // to itself twice, you can call: 399 // NewWord(f, "d3d", "dup dup + +") 400 // which does two dups, and two adds. 401 func NewWord(f Forth, name string, cell Cell, cells ...Cell) { 402 cmd := append([]Cell{cell}, cells...) 403 newword := func(f Forth) { 404 Eval(f, cmd...) 405 } 406 Putop(name, newword) 407 }