github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/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 // EmptyStack means we wanted something and ... nothing there. 71 ErrEmptyStack = errors.New("empty stack") 72 // NotEnoughElements means the stack is not deep enough for whatever operator we have. 73 ErrNotEnoughElements = errors.New("not enough elements on stack") 74 // ErrWordExist is the error for trying to create a word that's already in use. 75 ErrWordExist = errors.New("word already exists") 76 ) 77 78 func init() { 79 opmap = map[string]Op{ 80 "+": plus, 81 "-": sub, 82 "*": times, 83 "/": div, 84 "%": mod, 85 "swap": swap, 86 "ifelse": ifelse, 87 "hostname": hostname, 88 "hostbase": hostbase, 89 "strcat": strcat, 90 "roundup": roundup, 91 "dup": dup, 92 "drop": drop, 93 "newword": newword, 94 "words": words, 95 } 96 } 97 98 // Forth is an interface used by the package. The interface 99 // requires definition of Push, Pop, Length, Empty (convenience function 100 // meaning Length is 0), Newop (insert a new or replacement operator), 101 // and Reset (clear the stack, mainly diagnostic) 102 type Forth interface { 103 Push(Cell) 104 Pop() Cell 105 Length() int 106 Empty() bool 107 Reset() 108 Stack() []Cell 109 } 110 111 // New creates a new stack 112 func New() Forth { 113 f := new(stack) 114 return f 115 } 116 117 // Getop gets an op from the map. 118 func Getop(n string) Op { 119 mapLock.Lock() 120 defer mapLock.Unlock() 121 op, ok := opmap[n] 122 if !ok { 123 return nil 124 } 125 return op 126 } 127 128 // Putop creates a new operation. We considered having 129 // an opmap per stack but don't feel the package requires it 130 func Putop(n string, op Op) { 131 mapLock.Lock() 132 defer mapLock.Unlock() 133 if _, ok := opmap[n]; ok { 134 panic(fmt.Errorf("Putting %s: %w", n, ErrWordExist)) 135 } 136 opmap[n] = op 137 } 138 139 // Ops returns the operator map. 140 func Ops() map[string]Op { 141 return opmap 142 } 143 144 // Reset resets the stack to empty 145 func (f *stack) Reset() { 146 f.stack = f.stack[0:0] 147 } 148 149 // Return the stack 150 func (f *stack) Stack() []Cell { 151 return f.stack 152 } 153 154 // Push pushes the interface{} on the stack. 155 func (f *stack) Push(c Cell) { 156 f.stack = append(f.stack, c) 157 Debug("push: %v: stack: %v\n", c, f.stack) 158 } 159 160 // Pop pops the stack. If the stack is Empty Pop will panic. 161 // Eval recovers() the panic. 162 func (f *stack) Pop() (ret Cell) { 163 if len(f.stack) < 1 { 164 panic(ErrEmptyStack) 165 } 166 ret = f.stack[len(f.stack)-1] 167 f.stack = f.stack[0 : len(f.stack)-1] 168 Debug("Pop: %v stack %v\n", ret, f.stack) 169 return ret 170 } 171 172 // Length returns the stack length. 173 func (f *stack) Length() int { 174 return len(f.stack) 175 } 176 177 // Empty is a convenience function synonymous with Length == 0 178 func (f *stack) Empty() bool { 179 return len(f.stack) == 0 180 } 181 182 // errRecover converts panics to errstr iff it is an os.Error, panics 183 // otherwise. 184 func errRecover(errp *error) { 185 e := recover() 186 if e != nil { 187 if _, ok := e.(runtime.Error); ok { 188 Debug("errRecover panics with a runtime error") 189 panic(e) 190 } 191 Debug("errRecover returns %v", e) 192 *errp = e.(error) 193 } 194 } 195 196 // Eval takes a Forth and []Cell, pushing each element on the stack or invoking the 197 // operator if it is found in the opmap. 198 func eval(f Forth, cells ...Cell) { 199 Debug("eval cells %v", cells) 200 for _, c := range cells { 201 Debug("eval %v(%T) stack %v", c, c, f.Stack()) 202 switch s := c.(type) { 203 case string: 204 fun := Getop(s) 205 if fun != nil { 206 Debug("eval ... %v:", f.Stack()) 207 fun(f) 208 Debug("eval: Stack now %v", f.Stack()) 209 break 210 } 211 if s[0] == '\'' { 212 s = s[1:] 213 } 214 f.Push(s) 215 Debug("push %v(%T), stack %v", s, s, f.Stack()) 216 default: 217 Debug("push %v(%T), stack %v", s, s, f.Stack()) 218 f.Push(s) 219 } 220 } 221 } 222 223 // Eval calls eval and catches panics. 224 func Eval(f Forth, cells ...Cell) (err error) { 225 defer errRecover(&err) 226 eval(f, cells...) 227 return 228 } 229 230 // EvalString takes a Forth and string and splits the string on space 231 // characters, calling Eval for each one. 232 func EvalString(f Forth, s string) (err error) { 233 for _, c := range strings.Fields(s) { 234 if err = Eval(f, c); err != nil { 235 return 236 } 237 } 238 Debug("EvalString err %v", err) 239 return 240 } 241 242 // EvalPop takes a Forth and string, calls EvalString, and 243 // returns TOS and an error, if any. 244 // For EvalPop it is an error to leave the stack non-Empty. 245 // EvalPop is typically used for programs that want to 246 // parse forth contained in, e.g., flag.Args(), and return 247 // a result. In most use cases, we want the stack to be empty. 248 func EvalPop(f Forth, s string) (ret Cell, err error) { 249 defer errRecover(&err) 250 if err = EvalString(f, s); err != nil { 251 return 252 } 253 if f.Length() != 1 { 254 panic(fmt.Errorf("%v: length is not 1;%w", f.Stack(), strconv.ErrRange)) 255 } 256 ret = f.Pop() 257 Debug("EvalPop ret %v err %v", ret, err) 258 return 259 } 260 261 // String returns the Top Of Stack if it is a string 262 // or panics. 263 func String(f Forth) string { 264 c := f.Pop() 265 switch s := c.(type) { 266 case string: 267 return s 268 default: 269 panic(fmt.Errorf("%v:%w", c, strconv.ErrSyntax)) 270 } 271 } 272 273 // toInt converts to int64. 274 func toInt(f Forth) int64 { 275 Debug("toint %v", f.Stack()) 276 c := f.Pop() 277 Debug("%T", c) 278 switch s := c.(type) { 279 case string: 280 i, err := strconv.ParseInt(s, 0, 64) 281 if err != nil { 282 panic(err) 283 } 284 return i 285 case int64: 286 return s 287 default: 288 panic(fmt.Errorf("%v NaN: %T:%w", c, c, strconv.ErrSyntax)) 289 } 290 } 291 292 func plus(f Forth) { 293 x := toInt(f) 294 y := toInt(f) 295 z := x + y 296 f.Push(z) 297 } 298 299 func words(f Forth) { 300 mapLock.Lock() 301 defer mapLock.Unlock() 302 var w []string 303 for i := range opmap { 304 w = append(w, i) 305 } 306 f.Push(w) 307 } 308 309 func newword(f Forth) { 310 s := String(f) 311 n := toInt(f) 312 // Pop <n> Cells. 313 if int64(f.Length()) < n { 314 panic(fmt.Errorf("newword %s: stack is %d elements, need %d:%w", s, f.Length(), n, ErrNotEnoughElements)) 315 } 316 c := make([]Cell, n) 317 for i := n; i > 0; i-- { 318 c[i-1] = f.Pop() 319 } 320 Putop(s, func(f Forth) { 321 Debug("c %v", c) 322 eval(f, c...) 323 }) 324 } 325 326 func drop(f Forth) { 327 _ = f.Pop() 328 } 329 330 func times(f Forth) { 331 x := toInt(f) 332 y := toInt(f) 333 z := x * y 334 f.Push(z) 335 } 336 337 func sub(f Forth) { 338 x := toInt(f) 339 y := toInt(f) 340 z := y - x 341 f.Push(z) 342 } 343 344 func div(f Forth) { 345 x := toInt(f) 346 y := toInt(f) 347 z := y / x 348 f.Push(z) 349 } 350 351 func mod(f Forth) { 352 x := toInt(f) 353 y := toInt(f) 354 z := y % x 355 f.Push(z) 356 } 357 358 func roundup(f Forth) { 359 rnd := toInt(f) 360 v := toInt(f) 361 v = ((v + rnd - 1) / rnd) * rnd 362 f.Push(v) 363 } 364 365 func swap(f Forth) { 366 x := f.Pop() 367 y := f.Pop() 368 f.Push(x) 369 f.Push(y) 370 } 371 372 func strcat(f Forth) { 373 x := String(f) 374 y := String(f) 375 f.Push(y + x) 376 } 377 378 func dup(f Forth) { 379 x := f.Pop() 380 f.Push(x) 381 f.Push(x) 382 } 383 384 func ifelse(f Forth) { 385 x := toInt(f) 386 y := f.Pop() 387 z := f.Pop() 388 if x != 0 { 389 f.Push(y) 390 } else { 391 f.Push(z) 392 } 393 } 394 395 func hostname(f Forth) { 396 h, err := os.Hostname() 397 if err != nil { 398 panic(err) 399 } 400 f.Push(h) 401 } 402 403 func hostbase(f Forth) { 404 host := String(f) 405 f.Push(strings.TrimLeft(host, "abcdefghijklmnopqrstuvwxyz -")) 406 } 407 408 // NewWord allows for definition of new operators from strings. 409 // For example, should you wish to create a word which adds a number 410 // to itself twice, you can call: 411 // NewWord(f, "d3d", "dup dup + +") 412 // which does two dups, and two adds. 413 func NewWord(f Forth, name string, cell Cell, cells ...Cell) { 414 cmd := append([]Cell{cell}, cells...) 415 newword := func(f Forth) { 416 eval(f, cmd...) 417 } 418 Putop(name, newword) 419 }