How to rewind in Go's text/scanner?

1.1k Views Asked by At

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

1

There are 1 best solutions below

4
On

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.