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  }