blob: 69e8830abc8d5a7f8fac2fd320e8d1ad0beec631 [file] [log] [blame]
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package parse
import (
"bytes"
"unicode"
"unicode/utf8"
)
// Reader is the interface to an object that converts a rune array into tokens.
type Reader struct {
runes []rune // The string being parsed.
offset int // The start of the current token.
cursor int // The offset of the next unparsed rune.
}
func (r *Reader) setData(data string) {
r.runes = bytes.Runes([]byte(data))
}
// Token peeks at the current scanned token value. It does not consume anything.
func (r *Reader) Token() Token {
return Token{Runes: r.runes, Start: r.offset, End: r.cursor}
}
// Consume consumes the current token.
func (r *Reader) Consume() Token {
tok := r.Token()
r.offset = r.cursor
return tok
}
// Advance moves the cursor one rune forward.
func (r *Reader) Advance() {
if r.cursor < len(r.runes) {
r.cursor++
}
}
// Rollback sets the cursor back to the last consume point.
func (r *Reader) Rollback() {
r.cursor = r.offset
}
// IsEOF returns true when the cursor is at the end of the input.
func (r *Reader) IsEOF() bool {
return r.cursor >= len(r.runes)
}
// GuessNextToken attempts to do a general purpose consume of a single
// arbitrary token from the stream. It is used by error handlers to indicate
// where the error occurred. It guarantees that if the stream is not finished,
// it will consume at least one character.
func (r *Reader) GuessNextToken() Token {
if r.cursor == r.offset {
r.Space()
switch {
case r.AlphaNumeric():
case r.Numeric():
case r.NotSpace():
default:
r.Advance()
}
}
return r.Consume()
}
// Peek returns the next rune without advancing the cursor.
func (r *Reader) Peek() rune {
if r.cursor >= len(r.runes) {
return utf8.RuneError
}
return r.runes[r.cursor]
}
// Rune advances and returns true if the next rune after the cursor matches value.
func (r *Reader) Rune(value rune) bool {
if r.cursor >= len(r.runes) || r.runes[r.cursor] != value {
return false
}
r.cursor++
return true
}
// SeekRune advances the cursor until either the value is found or the end
// of stream is reached.
// It returns true if it found value, false otherwise.
func (r *Reader) SeekRune(value rune) bool {
for i := r.cursor; i < len(r.runes); i++ {
if r.runes[i] == value {
r.cursor = i
return true
}
}
return false
}
// String checks to see if value occurs at cursor, if it does, it advances the
// cursor past it and returns true.
func (r *Reader) String(value string) bool {
end := r.cursor + len(value)
if end > len(r.runes) {
return false
}
for i, v := range value {
if r.runes[r.cursor+i] != v {
return false
}
}
r.cursor = end
return true
}
// Space skips over any whitespace, returning true if it advanced the cursor.
func (r *Reader) Space() bool {
i := r.cursor
for ; i < len(r.runes); i++ {
r := r.runes[i]
if r == RuneEOL || !unicode.IsSpace(r) {
break
}
}
if i == r.cursor {
return false
}
r.cursor = i
return true
}
// Space skips over any non whitespace, returning true if it advanced the cursor.
func (r *Reader) NotSpace() bool {
i := r.cursor
for ; i < len(r.runes); i++ {
if unicode.IsSpace(r.runes[i]) {
break
}
}
if i == r.cursor {
return false
}
r.cursor = i
return true
}
// Numeric tries to move past the common number pattern, returning true if
// found and false if not.
func (r *Reader) Numeric() bool {
i := r.cursor
if i < len(r.runes) && (r.runes[i] == '+' || r.runes[i] == '-') {
i++
}
if i < len(r.runes) && unicode.IsDigit(r.runes[i]) {
for i++; i < len(r.runes); i++ {
r := r.runes[i]
if r != '-' && r != '+' && r != '.' && !unicode.IsLetter(r) && !unicode.IsDigit(r) {
break
}
}
}
if i == r.cursor {
return false
}
r.cursor = i
return true
}
// AlphaNumeric moves past anything that starts with a letter or underscore,
// and consists of letters, numbers or underscores. It returns true if the
// pattern was matched, false otherwise.
func (r *Reader) AlphaNumeric() bool {
i := r.cursor
if i >= len(r.runes) {
return false
}
next := r.runes[r.cursor]
if next == '_' || unicode.IsLetter(next) {
for i++; i < len(r.runes); i++ {
next := r.runes[i]
if next != '_' && !unicode.IsLetter(next) && !unicode.IsDigit(next) {
break
}
}
}
if i == r.cursor {
return false
}
r.cursor = i
return true
}