github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/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  }