package gval import ( "bytes" "fmt" "strings" "text/scanner" "unicode" ) //Parser parses expressions in a Language into an Evaluable type Parser struct { scanner scanner.Scanner Language lastScan rune camouflage error } func newParser(expression string, l Language) *Parser { sc := scanner.Scanner{} sc.Init(strings.NewReader(expression)) sc.Error = func(*scanner.Scanner, string) {} sc.Filename = expression + "\t" p := &Parser{scanner: sc, Language: l} p.resetScannerProperties() return p } func (p *Parser) resetScannerProperties() { p.scanner.Whitespace = scanner.GoWhitespace p.scanner.Mode = scanner.GoTokens p.scanner.IsIdentRune = func(r rune, pos int) bool { return unicode.IsLetter(r) || r == '_' || (pos > 0 && unicode.IsDigit(r)) } } // SetWhitespace sets the behavior of the whitespace matcher. The given // characters must be less than or equal to 0x20 (' '). func (p *Parser) SetWhitespace(chars ...rune) { var mask uint64 for _, char := range chars { mask |= 1 << char } p.scanner.Whitespace = mask } // SetMode sets the tokens that the underlying scanner will match. func (p *Parser) SetMode(mode uint) { p.scanner.Mode = mode } // SetIsIdentRuneFunc sets the function that matches ident characters in the // underlying scanner. func (p *Parser) SetIsIdentRuneFunc(fn func(ch rune, i int) bool) { p.scanner.IsIdentRune = fn } // Scan reads the next token or Unicode character from source and returns it. // It only recognizes tokens t for which the respective Mode bit (1<<-t) is set. // It returns scanner.EOF at the end of the source. func (p *Parser) Scan() rune { if p.isCamouflaged() { p.camouflage = nil return p.lastScan } p.camouflage = nil p.lastScan = p.scanner.Scan() return p.lastScan } func (p *Parser) isCamouflaged() bool { return p.camouflage != nil && p.camouflage != errCamouflageAfterNext } // Camouflage rewind the last Scan(). The Parser holds the camouflage error until // the next Scan() // Do not call Rewind() on a camouflaged Parser func (p *Parser) Camouflage(unit string, expected ...rune) { if p.isCamouflaged() { panic(fmt.Errorf("can only Camouflage() after Scan(): %w", p.camouflage)) } p.camouflage = p.Expected(unit, expected...) } // Peek returns the next Unicode character in the source without advancing // the scanner. It returns EOF if the scanner's position is at the last // character of the source. // Do not call Peek() on a camouflaged Parser func (p *Parser) Peek() rune { if p.isCamouflaged() { panic("can not Peek() on camouflaged Parser") } return p.scanner.Peek() } var errCamouflageAfterNext = fmt.Errorf("Camouflage() after Next()") // Next reads and returns the next Unicode character. // It returns EOF at the end of the source. // Do not call Next() on a camouflaged Parser func (p *Parser) Next() rune { if p.isCamouflaged() { panic("can not Next() on camouflaged Parser") } p.camouflage = errCamouflageAfterNext return p.scanner.Next() } // TokenText returns the string corresponding to the most recently scanned token. // Valid after calling Scan(). func (p *Parser) TokenText() string { return p.scanner.TokenText() } //Expected returns an error signaling an unexpected Scan() result func (p *Parser) Expected(unit string, expected ...rune) error { return unexpectedRune{unit, expected, p.lastScan} } type unexpectedRune struct { unit string expected []rune got rune } func (err unexpectedRune) Error() string { exp := bytes.Buffer{} runes := err.expected switch len(runes) { default: for _, r := range runes[:len(runes)-2] { exp.WriteString(scanner.TokenString(r)) exp.WriteString(", ") } fallthrough case 2: exp.WriteString(scanner.TokenString(runes[len(runes)-2])) exp.WriteString(" or ") fallthrough case 1: exp.WriteString(scanner.TokenString(runes[len(runes)-1])) case 0: return fmt.Sprintf("unexpected %s while scanning %s", scanner.TokenString(err.got), err.unit) } return fmt.Sprintf("unexpected %s while scanning %s expected %s", scanner.TokenString(err.got), err.unit, exp.String()) }