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  }