I am using text/scanner
package to parse some arbitrary expressions. I am currently trying to implement a not in
option, that is, if the current identifier is not
, and the next is in
, parse it using function notin(left, right)
, and otherwise we parse it as negate(right)
.
I've essentially got the code to manage these cases however, I am unable to rewind the scanner in case the next token is not in
. I've tried by recording the position and then reassigning it later, but to no avail and haven't been able to find a different solution.
func readToken(stream *scanner.Scanner) {
switch stream.Scan() {
case scanner.Ident:
switch stream.TokenText() {
case "in":
in(left, right)
case "not":
oldPosition := stream.Position
nextToken := stream.Scan()
if nextToken == scanner.Ident {
switch stream.TokenText() {
case "in":
fmt.Println("notin")
default:
// how do we rewind the scanner?
stream.Position = oldPosition
fmt.Println("negate default")
}
} else {
fmt.Println("negate no-ident")
}
}
}
}
How can I rewind the scanner when I don't find a valid identifier?
Edit, I also tried using Peek()
as below, but that still changes the state to the point that I'd need to rewind as well.
// other code
case "not":
nextIdent, err := getNextIdent(stream)
if err != nil {
fmt.Println("negate no-ident")
} else {
switch nextIdent {
case "in":
fmt.Println("notin")
default:
fmt.Println("negate default")
}
}
// other code
func getNextIdent(s *scanner.Scanner) (string, error) {
var nextIdent string
ch := s.Peek()
// skip white space
for s.Whitespace&(1<<uint(ch)) != 0 {
ch = s.Next()
}
if isIdentRune(ch, 0) {
nextIdent = string(ch)
ch = s.Next()
nextIdent += string(ch)
for i := 1; isIdentRune(ch, i); i++ {
ch = s.Next()
if s.Whitespace&(1<<uint(ch)) != 0 {
break
}
nextIdent += string(ch)
}
return nextIdent, nil
}
return "",errors.New("not a ident")
}
Note, the code I've got is a forked from Knetic/govaluate combined with a PR from GH user generikvault and some other forks. The full code can be found on my Github profile
By looking at the API references of
text/scanner
, I can't seem to find a way to rewind the scanner the way you want.However, the
Peek()
method would get you the next rune without advancing the scanner. Inside the "not" case, you can use it to peek in advance to see if it matches.