wfc-server/serverbrowser/filter/scanner.go
2024-02-06 17:51:52 -05:00

191 lines
3.9 KiB
Go

// Modified from github.com/zdebeer99/goexpression
package filter
import (
"strings"
"unicode/utf8"
)
// Pos represents a byte position in the original input text from which
// this template was parsed.
type Pos int
func (p Pos) Position() Pos {
return p
}
const eof = -1
// Scanner, Iterates through a string.
type Scanner struct {
input string
start Pos
pos Pos
width Pos
curr rune
prevState ScannerState
safebackup bool //insure backup is called only once after next.
}
// NewScanner Creates a New Scanner pointer.
func NewScanner(template string) *Scanner {
return &Scanner{input: template}
}
func (this *Scanner) StartPosition() int {
return int(this.start)
}
func (this *Scanner) SetPosition(pos int) {
this.pos = Pos(pos)
}
func (this *Scanner) SetStartPosition(pos int) {
this.start = Pos(pos)
}
// Token return the current selected text and move the start position to the current position
func (this *Scanner) Commit() string {
r1 := this.input[this.start:this.pos]
this.start = this.pos
this.prevState = this.SaveState()
return r1
}
// IsEOF check if the end of the current string has been reached.
func (this *Scanner) IsEOF() bool {
return int(this.pos) >= len(this.input)
}
func (this *Scanner) Size() int {
return len(this.input)
}
func (this *Scanner) MoveStart(pos int) {
this.start = this.start + Pos(pos)
}
// Next returns the next rune in the input.
func (this *Scanner) Next() rune {
this.safebackup = true
if this.IsEOF() {
this.width = 0
return eof
}
r, w := utf8.DecodeRuneInString(this.input[this.pos:])
this.width = Pos(w)
this.pos += this.width
this.curr = r
return r
}
func (this *Scanner) Skip() {
this.Next()
this.Commit()
}
// Peek returns but does not consume the next rune in the input.
func (this *Scanner) Peek() rune {
r := this.Next()
this.Backup()
return r
}
// Backup steps back one rune. Can only be called once per call of next.
func (this *Scanner) Backup() {
this.pos -= this.width
}
// Rollback move the curr pos back to the start pos.
func (this *Scanner) Rollback() {
this.LoadState(this.prevState)
}
// Ignore skips over the pending input before this point.
func (this *Scanner) Ignore() {
this.start = this.pos
}
// accept consumes the next rune if it's from the valid set.
func (this *Scanner) Accept(valid string) bool {
if strings.IndexRune(valid, this.Next()) >= 0 {
return true
}
this.Backup()
return false
}
// acceptRun consumes a run of runes from the valid set.
func (this *Scanner) AcceptRun(valid string) (found int) {
for strings.IndexRune(valid, this.Next()) >= 0 {
found++
}
this.Backup()
return found
}
// runTo consumes a run of runes until an item in the valid set is found.
func (this *Scanner) RunTo(valid string) rune {
for {
r := this.Next()
if r == eof {
return r
}
if strings.IndexRune(valid, r) >= 0 {
return r
}
}
this.Backup()
return eof
}
func (this *Scanner) Prefix(pre string) bool {
if strings.HasPrefix(this.input[this.pos:], pre) {
this.pos += Pos(len(pre))
return true
}
return false
}
func (this *Scanner) SkipSpaces() {
for IsSpace(this.Next()) {
}
this.Backup()
this.Ignore()
}
func (this *Scanner) SkipToNewLine() {
for {
r := this.Next()
if this.IsEOF() {
break
}
if r == '\n' {
break
}
}
this.Ignore()
return
}
// lineNumber reports which line we're on, based on the position of
// the previous item returned by nextItem. Doing it this way
// means we don't have to worry about peek double counting.
func (this *Scanner) LineNumber() int {
return 1 + strings.Count(this.input[:this.pos], "\n")
}
type ScannerState struct {
start Pos
pos Pos
width Pos
}
func (this *Scanner) SaveState() ScannerState {
return ScannerState{start: this.start, pos: this.pos, width: this.width}
}
func (this *Scanner) LoadState(state ScannerState) {
this.start, this.pos, this.width = state.start, state.pos, state.width
}