github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/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  // the forth package is designed for use by programs
     6  // needing to evaluate command-line arguments or simple
     7  // expressions to set program variables. It is designed
     8  // to map host names to numbers. We use it to
     9  // easily convert host names and IP addresses into
    10  // parameters.
    11  // The language
    12  // is a Forth-like postfix notation. Elements are
    13  // either commands or strings. Strings are
    14  // immediately pushed. Commands consume stack variables
    15  // and produce new ones.
    16  // Simple examples:
    17  // push hostname, strip alpha characters to produce a number. If your
    18  // hostname is sb47, top of stack will be left with 47.
    19  // hostname  hostbase
    20  // Get the hostbase, if it is 0 mod 20, return the hostbase / 20,
    21  // else return hostbase mod 20
    22  //
    23  // hostname hostbase dup 20 / swap 20 % dup ifelse
    24  //
    25  // At the end of the evaluation the stack should have one element
    26  // left; that element is popped and returned. It is an error (currently)
    27  // to return with a non-empty stack.
    28  // This package was used for real work at Sandia National Labs from 2010 to 2012 and possibly later.
    29  // Some of the use of error may seem a bit weird but the creation of this package predates the
    30  // creation of the error type (it was still an os thing back then).
    31  package forth
    32  
    33  import (
    34  	"errors"
    35  	"os"
    36  	"runtime"
    37  	"strconv"
    38  	"strings"
    39  )
    40  
    41  type ForthOp func(f Forth)
    42  
    43  type forthstack struct {
    44  	stack []string
    45  }
    46  
    47  var opmap = map[string]ForthOp{
    48  	"+":        plus,
    49  	"-":        sub,
    50  	"*":        times,
    51  	"/":        div,
    52  	"%":        mod,
    53  	"swap":     swap,
    54  	"ifelse":   ifelse,
    55  	"hostname": hostname,
    56  	"hostbase": hostbase,
    57  	"strcat":   strcat,
    58  	"roundup":  roundup,
    59  	"dup":      dup,
    60  }
    61  
    62  // Forth is an interface used by the package. The interface
    63  // requires definition of Push, Pop, Length, Empty (convenience function
    64  // meaning Length is 0), Newop (insert a new or replacement operator),
    65  // and Reset (clear the stack, mainly diagnostic)
    66  type Forth interface {
    67  	Push(string)
    68  	Pop() string
    69  	Length() int
    70  	Empty() bool
    71  	Newop(string, ForthOp)
    72  	Reset()
    73  	Stack() []string
    74  }
    75  
    76  // New creates a new stack
    77  func New() Forth {
    78  	f := new(forthstack)
    79  	return f
    80  }
    81  
    82  // Newop creates a new operation. We considered having
    83  // an opmap per stack but don't feel the package requires it
    84  func (f *forthstack) Newop(n string, op ForthOp) {
    85  	opmap[n] = op
    86  }
    87  
    88  func Ops() map[string]ForthOp {
    89  	return opmap
    90  }
    91  
    92  // Reset resets the stack to empty
    93  func (f *forthstack) Reset() {
    94  	f.stack = f.stack[0:0]
    95  }
    96  
    97  // Return the stack as a []string
    98  func (f *forthstack) Stack() []string {
    99  	return f.stack
   100  }
   101  
   102  // Push pushes the string on the stack.
   103  func (f *forthstack) Push(s string) {
   104  	f.stack = append(f.stack, s)
   105  	//fmt.Printf("push: %v: stack: %v\n", s, f.stack)
   106  }
   107  
   108  // Pop pops the stack. If the stack is Empty Pop will panic.
   109  // Eval recovers() the panic.
   110  func (f *forthstack) Pop() (ret string) {
   111  
   112  	if len(f.stack) < 1 {
   113  		panic(errors.New("Empty stack"))
   114  	}
   115  	ret = f.stack[len(f.stack)-1]
   116  	f.stack = f.stack[0 : len(f.stack)-1]
   117  	//fmt.Printf("Pop: %v stack %v\n", ret, f.stack)
   118  	return ret
   119  }
   120  
   121  // Length returns the stack length.
   122  func (f *forthstack) Length() int {
   123  	return len(f.stack)
   124  }
   125  
   126  // Empty is a convenience function synonymous with Length == 0
   127  func (f *forthstack) Empty() bool {
   128  	return len(f.stack) == 0
   129  }
   130  
   131  // errRecover converts panics to errstr iff it is an os.Error, panics
   132  // otherwise.
   133  func errRecover(errp *error) {
   134  	e := recover()
   135  	if e != nil {
   136  		if _, ok := e.(runtime.Error); ok {
   137  			panic(e)
   138  		}
   139  		*errp = e.(error)
   140  	}
   141  }
   142  
   143  /* iEval takes a Forth and strings and splits the string on space
   144   * characters, pushing each element on the stack or invoking the
   145   * operator if it is found in the opmap.
   146   */
   147  func iEval(f Forth, s string) {
   148  	// TODO: create a separator list based on isspace and all the
   149  	// non alpha numberic characters in the opmap.
   150  	for _, val := range strings.Fields(s) {
   151  		//fmt.Printf("eval %s stack %v", val, f.Stack())
   152  		fun := opmap[val]
   153  		if fun != nil {
   154  			//fmt.Printf("Eval ...:")
   155  			fun(f)
   156  			//fmt.Printf("Stack now %v", f.Stack())
   157  		} else {
   158  			f.Push(val)
   159  			//fmt.Printf("push %s, stack %v", val, f.Stack())
   160  		}
   161  	}
   162  	return
   163  }
   164  
   165  /* Eval takes a Forth and strings and splits the string on space
   166   * characters, pushing each element on the stack or invoking the
   167   * operator if it is found in the opmap. It returns TOS when it is done.
   168   * it is an error to leave the stack non-Empty.
   169   */
   170  func Eval(f Forth, s string) (ret string, err error) {
   171  	defer errRecover(&err)
   172  	iEval(f, s)
   173  	ret = f.Pop()
   174  	return
   175  
   176  }
   177  
   178  // toInt converts to int64.
   179  func toInt(f Forth) int64 {
   180  	i, err := strconv.ParseInt(f.Pop(), 0, 64)
   181  	if err != nil {
   182  		panic(err)
   183  	}
   184  	return i
   185  }
   186  
   187  func plus(f Forth) {
   188  	x := toInt(f)
   189  	y := toInt(f)
   190  	z := x + y
   191  	f.Push(strconv.FormatInt(z, 10))
   192  }
   193  
   194  func times(f Forth) {
   195  	x := toInt(f)
   196  	y := toInt(f)
   197  	z := x * y
   198  	f.Push(strconv.FormatInt(z, 10))
   199  }
   200  
   201  func sub(f Forth) {
   202  	x := toInt(f)
   203  	y := toInt(f)
   204  	z := y - x
   205  	f.Push(strconv.FormatInt(z, 10))
   206  }
   207  
   208  func div(f Forth) {
   209  	x := toInt(f)
   210  	y := toInt(f)
   211  	z := y / x
   212  	f.Push(strconv.FormatInt(z, 10))
   213  }
   214  
   215  func mod(f Forth) {
   216  	x := toInt(f)
   217  	y := toInt(f)
   218  	z := y % x
   219  	f.Push(strconv.FormatInt(z, 10))
   220  }
   221  
   222  func roundup(f Forth) {
   223  	rnd := toInt(f)
   224  	v := toInt(f)
   225  	v = ((v + rnd - 1) / rnd) * rnd
   226  	f.Push(strconv.FormatInt(v, 10))
   227  }
   228  
   229  func swap(f Forth) {
   230  	x := f.Pop()
   231  	y := f.Pop()
   232  	f.Push(x)
   233  	f.Push(y)
   234  }
   235  
   236  func strcat(f Forth) {
   237  	x := f.Pop()
   238  	y := f.Pop()
   239  	f.Push(y + x)
   240  }
   241  
   242  func dup(f Forth) {
   243  	x := f.Pop()
   244  	f.Push(x)
   245  	f.Push(x)
   246  }
   247  
   248  func ifelse(f Forth) {
   249  	x := toInt(f)
   250  	y := f.Pop()
   251  	z := f.Pop()
   252  	if x != 0 {
   253  		f.Push(y)
   254  	} else {
   255  		f.Push(z)
   256  	}
   257  }
   258  
   259  func hostname(f Forth) {
   260  	h, err := os.Hostname()
   261  	if err != nil {
   262  		panic("No hostname")
   263  	}
   264  	f.Push(h)
   265  }
   266  
   267  func hostbase(f Forth) {
   268  	host := f.Pop()
   269  	f.Push(strings.TrimLeft(host, "abcdefghijklmnopqrstuvwxyz -"))
   270  }
   271  
   272  func NewWord(f Forth, name, command string) {
   273  	newword := func(f Forth) {
   274  		iEval(f, command)
   275  		return
   276  	}
   277  	opmap[name] = newword
   278  	return
   279  }