github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/examples/gno.land/p/demo/math_eval/int32/int32.gno (about)

     1  // eval/int32 is a evaluator for int32 expressions.
     2  // This code is heavily forked from https://github.com/dengsgo/math-engine
     3  // which is licensed under Apache 2.0:
     4  // https://raw.githubusercontent.com/dengsgo/math-engine/298e2b57b7e7350d0f67bd036916efd5709abe25/LICENSE
     5  package int32
     6  
     7  import (
     8  	"errors"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"gno.land/p/demo/ufmt"
    13  )
    14  
    15  const (
    16  	Identifier = iota
    17  	Number     // numbers
    18  	Operator   // +, -, *, /, etc.
    19  	Variable   // x, y, z, etc. (one-letter only)
    20  )
    21  
    22  type expression interface {
    23  	String() string
    24  }
    25  
    26  type expressionRaw struct {
    27  	expression string
    28  	Type       int
    29  	Flag       int
    30  	Offset     int
    31  }
    32  
    33  type parser struct {
    34  	Input  string
    35  	ch     byte
    36  	offset int
    37  	err    error
    38  }
    39  
    40  type expressionNumber struct {
    41  	Val int
    42  	Str string
    43  }
    44  
    45  type expressionVariable struct {
    46  	Val int
    47  	Str string
    48  }
    49  
    50  type expressionOperation struct {
    51  	Op string
    52  	Lhs,
    53  	Rhs expression
    54  }
    55  
    56  type ast struct {
    57  	rawexpressions    []*expressionRaw
    58  	source            string
    59  	currentexpression *expressionRaw
    60  	currentIndex      int
    61  	depth             int
    62  	err               error
    63  }
    64  
    65  // Parse takes an expression string, e.g. "1+2" and returns
    66  // a parsed expression. If there is an error it will return.
    67  func Parse(s string) (ar expression, err error) {
    68  	toks, err := lexer(s)
    69  	if err != nil {
    70  		return
    71  	}
    72  	ast, err := newAST(toks, s)
    73  	if err != nil {
    74  		return
    75  	}
    76  	ar, err = ast.parseExpression()
    77  	return
    78  }
    79  
    80  // Eval takes a parsed expression and a map of variables (or nil). The parsed
    81  // expression is evaluated using any variables and returns the
    82  // resulting int and/or error.
    83  func Eval(expr expression, variables map[string]int) (res int, err error) {
    84  	if err != nil {
    85  		return
    86  	}
    87  	var l, r int
    88  	switch expr.(type) {
    89  	case expressionVariable:
    90  		ast := expr.(expressionVariable)
    91  		ok := false
    92  		if variables != nil {
    93  			res, ok = variables[ast.Str]
    94  		}
    95  		if !ok {
    96  			err = ufmt.Errorf("variable '%s' not found", ast.Str)
    97  		}
    98  		return
    99  	case expressionOperation:
   100  		ast := expr.(expressionOperation)
   101  		l, err = Eval(ast.Lhs, variables)
   102  		if err != nil {
   103  			return
   104  		}
   105  		r, err = Eval(ast.Rhs, variables)
   106  		if err != nil {
   107  			return
   108  		}
   109  		switch ast.Op {
   110  		case "+":
   111  			res = l + r
   112  		case "-":
   113  			res = l - r
   114  		case "*":
   115  			res = l * r
   116  		case "/":
   117  			if r == 0 {
   118  				err = ufmt.Errorf("violation of arithmetic specification: a division by zero in Eval: [%d/%d]", l, r)
   119  				return
   120  			}
   121  			res = l / r
   122  		case "%":
   123  			if r == 0 {
   124  				res = 0
   125  			} else {
   126  				res = l % r
   127  			}
   128  		case "^":
   129  			res = l ^ r
   130  		case ">>":
   131  			res = l >> r
   132  		case "<<":
   133  			res = l << r
   134  		case ">":
   135  			if l > r {
   136  				res = 1
   137  			} else {
   138  				res = 0
   139  			}
   140  		case "<":
   141  			if l < r {
   142  				res = 1
   143  			} else {
   144  				res = 0
   145  			}
   146  		case "&":
   147  			res = l & r
   148  		case "|":
   149  			res = l | r
   150  		default:
   151  
   152  		}
   153  	case expressionNumber:
   154  		res = expr.(expressionNumber).Val
   155  	}
   156  
   157  	return
   158  }
   159  
   160  func expressionError(s string, pos int) string {
   161  	r := strings.Repeat("-", len(s)) + "\n"
   162  	s += "\n"
   163  	for i := 0; i < pos; i++ {
   164  		s += " "
   165  	}
   166  	s += "^\n"
   167  	return r + s + r
   168  }
   169  
   170  func (n expressionVariable) String() string {
   171  	return ufmt.Sprintf(
   172  		"expressionVariable: %s",
   173  		n.Str,
   174  	)
   175  }
   176  
   177  func (n expressionNumber) String() string {
   178  	return ufmt.Sprintf(
   179  		"expressionNumber: %s",
   180  		n.Str,
   181  	)
   182  }
   183  
   184  func (b expressionOperation) String() string {
   185  	return ufmt.Sprintf(
   186  		"expressionOperation: (%s %s %s)",
   187  		b.Op,
   188  		b.Lhs.String(),
   189  		b.Rhs.String(),
   190  	)
   191  }
   192  
   193  func newAST(toks []*expressionRaw, s string) (*ast, error) {
   194  	a := &ast{
   195  		rawexpressions: toks,
   196  		source:         s,
   197  	}
   198  	if a.rawexpressions == nil || len(a.rawexpressions) == 0 {
   199  		return a, errors.New("empty token")
   200  	} else {
   201  		a.currentIndex = 0
   202  		a.currentexpression = a.rawexpressions[0]
   203  	}
   204  	return a, nil
   205  }
   206  
   207  func (a *ast) parseExpression() (expression, error) {
   208  	a.depth++ // called depth
   209  	lhs := a.parsePrimary()
   210  	r := a.parseBinOpRHS(0, lhs)
   211  	a.depth--
   212  	if a.depth == 0 && a.currentIndex != len(a.rawexpressions) && a.err == nil {
   213  		return r, ufmt.Errorf("bad expression, reaching the end or missing the operator\n%s",
   214  			expressionError(a.source, a.currentexpression.Offset))
   215  	}
   216  	return r, nil
   217  }
   218  
   219  func (a *ast) getNextexpressionRaw() *expressionRaw {
   220  	a.currentIndex++
   221  	if a.currentIndex < len(a.rawexpressions) {
   222  		a.currentexpression = a.rawexpressions[a.currentIndex]
   223  		return a.currentexpression
   224  	}
   225  	return nil
   226  }
   227  
   228  func (a *ast) getTokPrecedence() int {
   229  	switch a.currentexpression.expression {
   230  	case "/", "%", "*":
   231  		return 100
   232  	case "<<", ">>":
   233  		return 80
   234  	case "+", "-":
   235  		return 75
   236  	case "<", ">":
   237  		return 70
   238  	case "&":
   239  		return 60
   240  	case "^":
   241  		return 50
   242  	case "|":
   243  		return 40
   244  	}
   245  	return -1
   246  }
   247  
   248  func (a *ast) parseNumber() expressionNumber {
   249  	f64, err := strconv.Atoi(a.currentexpression.expression)
   250  	if err != nil {
   251  		a.err = ufmt.Errorf("%v\nwant '(' or '0-9' but get '%s'\n%s",
   252  			err.Error(),
   253  			a.currentexpression.expression,
   254  			expressionError(a.source, a.currentexpression.Offset))
   255  		return expressionNumber{}
   256  	}
   257  	n := expressionNumber{
   258  		Val: f64,
   259  		Str: a.currentexpression.expression,
   260  	}
   261  	a.getNextexpressionRaw()
   262  	return n
   263  }
   264  
   265  func (a *ast) parseVariable() expressionVariable {
   266  	n := expressionVariable{
   267  		Val: 0,
   268  		Str: a.currentexpression.expression,
   269  	}
   270  	a.getNextexpressionRaw()
   271  	return n
   272  }
   273  
   274  func (a *ast) parsePrimary() expression {
   275  	switch a.currentexpression.Type {
   276  	case Variable:
   277  		return a.parseVariable()
   278  	case Number:
   279  		return a.parseNumber()
   280  	case Operator:
   281  		if a.currentexpression.expression == "(" {
   282  			t := a.getNextexpressionRaw()
   283  			if t == nil {
   284  				a.err = ufmt.Errorf("want '(' or '0-9' but get EOF\n%s",
   285  					expressionError(a.source, a.currentexpression.Offset))
   286  				return nil
   287  			}
   288  			e, _ := a.parseExpression()
   289  			if e == nil {
   290  				return nil
   291  			}
   292  			if a.currentexpression.expression != ")" {
   293  				a.err = ufmt.Errorf("want ')' but get %s\n%s",
   294  					a.currentexpression.expression,
   295  					expressionError(a.source, a.currentexpression.Offset))
   296  				return nil
   297  			}
   298  			a.getNextexpressionRaw()
   299  			return e
   300  		} else if a.currentexpression.expression == "-" {
   301  			if a.getNextexpressionRaw() == nil {
   302  				a.err = ufmt.Errorf("want '0-9' but get '-'\n%s",
   303  					expressionError(a.source, a.currentexpression.Offset))
   304  				return nil
   305  			}
   306  			bin := expressionOperation{
   307  				Op:  "-",
   308  				Lhs: expressionNumber{},
   309  				Rhs: a.parsePrimary(),
   310  			}
   311  			return bin
   312  		} else {
   313  			return a.parseNumber()
   314  		}
   315  	default:
   316  		return nil
   317  	}
   318  }
   319  
   320  func (a *ast) parseBinOpRHS(execPrec int, lhs expression) expression {
   321  	for {
   322  		tokPrec := a.getTokPrecedence()
   323  		if tokPrec < execPrec {
   324  			return lhs
   325  		}
   326  		binOp := a.currentexpression.expression
   327  		if a.getNextexpressionRaw() == nil {
   328  			a.err = ufmt.Errorf("want '(' or '0-9' but get EOF\n%s",
   329  				expressionError(a.source, a.currentexpression.Offset))
   330  			return nil
   331  		}
   332  		rhs := a.parsePrimary()
   333  		if rhs == nil {
   334  			return nil
   335  		}
   336  		nextPrec := a.getTokPrecedence()
   337  		if tokPrec < nextPrec {
   338  			rhs = a.parseBinOpRHS(tokPrec+1, rhs)
   339  			if rhs == nil {
   340  				return nil
   341  			}
   342  		}
   343  		lhs = expressionOperation{
   344  			Op:  binOp,
   345  			Lhs: lhs,
   346  			Rhs: rhs,
   347  		}
   348  	}
   349  }
   350  
   351  func lexer(s string) ([]*expressionRaw, error) {
   352  	p := &parser{
   353  		Input: s,
   354  		err:   nil,
   355  		ch:    s[0],
   356  	}
   357  	toks := p.parse()
   358  	if p.err != nil {
   359  		return nil, p.err
   360  	}
   361  	return toks, nil
   362  }
   363  
   364  func (p *parser) parse() []*expressionRaw {
   365  	toks := make([]*expressionRaw, 0)
   366  	for {
   367  		tok := p.nextTok()
   368  		if tok == nil {
   369  			break
   370  		}
   371  		toks = append(toks, tok)
   372  	}
   373  	return toks
   374  }
   375  
   376  func (p *parser) nextTok() *expressionRaw {
   377  	if p.offset >= len(p.Input) || p.err != nil {
   378  		return nil
   379  	}
   380  	var err error
   381  	for p.isWhitespace(p.ch) && err == nil {
   382  		err = p.nextCh()
   383  	}
   384  	start := p.offset
   385  	var tok *expressionRaw
   386  	switch p.ch {
   387  	case
   388  		'(',
   389  		')',
   390  		'+',
   391  		'-',
   392  		'*',
   393  		'/',
   394  		'^',
   395  		'&',
   396  		'|',
   397  		'%':
   398  		tok = &expressionRaw{
   399  			expression: string(p.ch),
   400  			Type:       Operator,
   401  		}
   402  		tok.Offset = start
   403  		err = p.nextCh()
   404  	case '>', '<':
   405  		tokS := string(p.ch)
   406  		bb, be := p.nextChPeek()
   407  		if be == nil && string(bb) == tokS {
   408  			tokS += string(p.ch)
   409  		}
   410  		tok = &expressionRaw{
   411  			expression: tokS,
   412  			Type:       Operator,
   413  		}
   414  		tok.Offset = start
   415  		if len(tokS) > 1 {
   416  			p.nextCh()
   417  		}
   418  		err = p.nextCh()
   419  	case
   420  		'0',
   421  		'1',
   422  		'2',
   423  		'3',
   424  		'4',
   425  		'5',
   426  		'6',
   427  		'7',
   428  		'8',
   429  		'9':
   430  		for p.isDigitNum(p.ch) && p.nextCh() == nil {
   431  			if (p.ch == '-' || p.ch == '+') && p.Input[p.offset-1] != 'e' {
   432  				break
   433  			}
   434  		}
   435  		tok = &expressionRaw{
   436  			expression: strings.ReplaceAll(p.Input[start:p.offset], "_", ""),
   437  			Type:       Number,
   438  		}
   439  		tok.Offset = start
   440  	default:
   441  		if p.isChar(p.ch) {
   442  			tok = &expressionRaw{
   443  				expression: string(p.ch),
   444  				Type:       Variable,
   445  			}
   446  			tok.Offset = start
   447  			err = p.nextCh()
   448  		} else if p.ch != ' ' {
   449  			p.err = ufmt.Errorf("symbol error: unknown '%v', pos [%v:]\n%s",
   450  				string(p.ch),
   451  				start,
   452  				expressionError(p.Input, start))
   453  		}
   454  	}
   455  	return tok
   456  }
   457  
   458  func (p *parser) nextChPeek() (byte, error) {
   459  	offset := p.offset + 1
   460  	if offset < len(p.Input) {
   461  		return p.Input[offset], nil
   462  	}
   463  	return byte(0), errors.New("no byte")
   464  }
   465  
   466  func (p *parser) nextCh() error {
   467  	p.offset++
   468  	if p.offset < len(p.Input) {
   469  		p.ch = p.Input[p.offset]
   470  		return nil
   471  	}
   472  	return errors.New("EOF")
   473  }
   474  
   475  func (p *parser) isWhitespace(c byte) bool {
   476  	return c == ' ' ||
   477  		c == '\t' ||
   478  		c == '\n' ||
   479  		c == '\v' ||
   480  		c == '\f' ||
   481  		c == '\r'
   482  }
   483  
   484  func (p *parser) isDigitNum(c byte) bool {
   485  	return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+'
   486  }
   487  
   488  func (p *parser) isChar(c byte) bool {
   489  	return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
   490  }
   491  
   492  func (p *parser) isWordChar(c byte) bool {
   493  	return p.isChar(c) || '0' <= c && c <= '9'
   494  }