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.
99 lines
2.1 KiB
99 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 |
|
}
|
|
|