asciigoat's core library https://asciigoat.org/core
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

100 lines
2.1 KiB

package scanner
import (
"unicode/utf8"
)
const (
// EOF is a dummy rune representing End-Of-File
EOF = -1
)
// A Position in the input string and in the line-based document
type Position struct {
Offset uint
Line, Column uint
}
// An Scanner represent the low level layer for text parsers
type Scanner struct {
name string
input string
base Position
cursor Position
runes uint
}
// NewScannerFromString instantiates a new Scanner to
// parse a given string
func NewScannerFromString(name, input string) *Scanner {
return &Scanner{
name: name,
input: input,
base: Position{0, 1, 1},
cursor: Position{0, 1, 1},
runes: 0,
}
}
// Length returns the number of bytes and runes in the Terminal that is been detected
func (l *Scanner) Length() (uint, uint) {
return l.cursor.Offset - l.base.Offset, l.runes
}
// Empty tells if there are no runes accounted for the next Terminal yet
func (l *Scanner) Empty() bool {
return l.runes == 0
}
// StepForth moves the cursor forward
func (l *Scanner) StepForth(runes, bytes uint) {
l.cursor.Offset += bytes
l.cursor.Column += runes
l.runes += runes
}
// StepBack moves the cursor backward
func (l *Scanner) StepBack(runes, bytes uint) {
l.cursor.Offset -= bytes
// FIXME: what if column goes < 1?
l.cursor.Column -= runes
l.runes -= runes
}
// Reset moves the cursor back to the base
func (l *Scanner) Reset() {
l.cursor = l.base
l.runes = 0
}
// Skip trashes everything up to the cursor
func (l *Scanner) Skip() {
l.base = l.cursor
l.runes = 0
}
// NewLine accounts a line break in the position of the cursor
func (l *Scanner) NewLine() {
l.cursor.Line++
l.cursor.Column = 1
}
// Peek returns the next rune but not moving the cursor
func (l *Scanner) Peek() (rune, uint) {
if l.cursor.Offset == uint(len(l.input)) {
return EOF, 0
}
r, bytes := utf8.DecodeRuneInString(l.input[l.cursor.Offset:])
return r, uint(bytes)
}
// Next returns the next rune but moving the cursor
func (l *Scanner) Next() (rune, uint) {
r, bytes := l.Peek()
if bytes > 0 {
l.StepForth(1, bytes)
}
return r, bytes
}