int32.gno

9.37 Kb ยท 494 lines
  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
  5package int32
  6
  7import (
  8	"errors"
  9	"strconv"
 10	"strings"
 11
 12	"gno.land/p/demo/ufmt"
 13)
 14
 15const (
 16	Identifier = iota
 17	Number     // numbers
 18	Operator   // +, -, *, /, etc.
 19	Variable   // x, y, z, etc. (one-letter only)
 20)
 21
 22type expression interface {
 23	String() string
 24}
 25
 26type expressionRaw struct {
 27	expression string
 28	Type       int
 29	Flag       int
 30	Offset     int
 31}
 32
 33type parser struct {
 34	Input  string
 35	ch     byte
 36	offset int
 37	err    error
 38}
 39
 40type expressionNumber struct {
 41	Val int
 42	Str string
 43}
 44
 45type expressionVariable struct {
 46	Val int
 47	Str string
 48}
 49
 50type expressionOperation struct {
 51	Op string
 52	Lhs,
 53	Rhs expression
 54}
 55
 56type 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.
 67func 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.
 83func 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
160func 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
170func (n expressionVariable) String() string {
171	return ufmt.Sprintf(
172		"expressionVariable: %s",
173		n.Str,
174	)
175}
176
177func (n expressionNumber) String() string {
178	return ufmt.Sprintf(
179		"expressionNumber: %s",
180		n.Str,
181	)
182}
183
184func (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
193func 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
207func (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
219func (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
228func (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
248func (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
265func (a *ast) parseVariable() expressionVariable {
266	n := expressionVariable{
267		Val: 0,
268		Str: a.currentexpression.expression,
269	}
270	a.getNextexpressionRaw()
271	return n
272}
273
274func (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
320func (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
351func 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
364func (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
376func (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
458func (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
466func (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
475func (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
484func (p *parser) isDigitNum(c byte) bool {
485	return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+'
486}
487
488func (p *parser) isChar(c byte) bool {
489	return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
490}
491
492func (p *parser) isWordChar(c byte) bool {
493	return p.isChar(c) || '0' <= c && c <= '9'
494}