123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- // Copyright 2013-2022 Frank Schroeder. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- //
- // Parts of the lexer are from the template/text/parser package
- // For these parts the following applies:
- //
- // Copyright 2011 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file of the go 1.2
- // distribution.
- package properties
- import (
- "fmt"
- "strconv"
- "strings"
- "unicode/utf8"
- )
- // item represents a token or text string returned from the scanner.
- type item struct {
- typ itemType // The type of this item.
- pos int // The starting position, in bytes, of this item in the input string.
- val string // The value of this item.
- }
- func (i item) String() string {
- switch {
- case i.typ == itemEOF:
- return "EOF"
- case i.typ == itemError:
- return i.val
- case len(i.val) > 10:
- return fmt.Sprintf("%.10q...", i.val)
- }
- return fmt.Sprintf("%q", i.val)
- }
- // itemType identifies the type of lex items.
- type itemType int
- const (
- itemError itemType = iota // error occurred; value is text of error
- itemEOF
- itemKey // a key
- itemValue // a value
- itemComment // a comment
- )
- // defines a constant for EOF
- const eof = -1
- // permitted whitespace characters space, FF and TAB
- const whitespace = " \f\t"
- // stateFn represents the state of the scanner as a function that returns the next state.
- type stateFn func(*lexer) stateFn
- // lexer holds the state of the scanner.
- type lexer struct {
- input string // the string being scanned
- state stateFn // the next lexing function to enter
- pos int // current position in the input
- start int // start position of this item
- width int // width of last rune read from input
- lastPos int // position of most recent item returned by nextItem
- runes []rune // scanned runes for this item
- items chan item // channel of scanned items
- }
- // next returns the next rune in the input.
- func (l *lexer) next() rune {
- if l.pos >= len(l.input) {
- l.width = 0
- return eof
- }
- r, w := utf8.DecodeRuneInString(l.input[l.pos:])
- l.width = w
- l.pos += l.width
- return r
- }
- // peek returns but does not consume the next rune in the input.
- func (l *lexer) peek() rune {
- r := l.next()
- l.backup()
- return r
- }
- // backup steps back one rune. Can only be called once per call of next.
- func (l *lexer) backup() {
- l.pos -= l.width
- }
- // emit passes an item back to the client.
- func (l *lexer) emit(t itemType) {
- i := item{t, l.start, string(l.runes)}
- l.items <- i
- l.start = l.pos
- l.runes = l.runes[:0]
- }
- // ignore skips over the pending input before this point.
- func (l *lexer) ignore() {
- l.start = l.pos
- }
- // appends the rune to the current value
- func (l *lexer) appendRune(r rune) {
- l.runes = append(l.runes, r)
- }
- // accept consumes the next rune if it's from the valid set.
- func (l *lexer) accept(valid string) bool {
- if strings.ContainsRune(valid, l.next()) {
- return true
- }
- l.backup()
- return false
- }
- // acceptRun consumes a run of runes from the valid set.
- func (l *lexer) acceptRun(valid string) {
- for strings.ContainsRune(valid, l.next()) {
- }
- l.backup()
- }
- // 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 (l *lexer) lineNumber() int {
- return 1 + strings.Count(l.input[:l.lastPos], "\n")
- }
- // errorf returns an error token and terminates the scan by passing
- // back a nil pointer that will be the next state, terminating l.nextItem.
- func (l *lexer) errorf(format string, args ...interface{}) stateFn {
- l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
- return nil
- }
- // nextItem returns the next item from the input.
- func (l *lexer) nextItem() item {
- i := <-l.items
- l.lastPos = i.pos
- return i
- }
- // lex creates a new scanner for the input string.
- func lex(input string) *lexer {
- l := &lexer{
- input: input,
- items: make(chan item),
- runes: make([]rune, 0, 32),
- }
- go l.run()
- return l
- }
- // run runs the state machine for the lexer.
- func (l *lexer) run() {
- for l.state = lexBeforeKey(l); l.state != nil; {
- l.state = l.state(l)
- }
- }
- // state functions
- // lexBeforeKey scans until a key begins.
- func lexBeforeKey(l *lexer) stateFn {
- switch r := l.next(); {
- case isEOF(r):
- l.emit(itemEOF)
- return nil
- case isEOL(r):
- l.ignore()
- return lexBeforeKey
- case isComment(r):
- return lexComment
- case isWhitespace(r):
- l.ignore()
- return lexBeforeKey
- default:
- l.backup()
- return lexKey
- }
- }
- // lexComment scans a comment line. The comment character has already been scanned.
- func lexComment(l *lexer) stateFn {
- l.acceptRun(whitespace)
- l.ignore()
- for {
- switch r := l.next(); {
- case isEOF(r):
- l.ignore()
- l.emit(itemEOF)
- return nil
- case isEOL(r):
- l.emit(itemComment)
- return lexBeforeKey
- default:
- l.appendRune(r)
- }
- }
- }
- // lexKey scans the key up to a delimiter
- func lexKey(l *lexer) stateFn {
- var r rune
- Loop:
- for {
- switch r = l.next(); {
- case isEscape(r):
- err := l.scanEscapeSequence()
- if err != nil {
- return l.errorf(err.Error())
- }
- case isEndOfKey(r):
- l.backup()
- break Loop
- case isEOF(r):
- break Loop
- default:
- l.appendRune(r)
- }
- }
- if len(l.runes) > 0 {
- l.emit(itemKey)
- }
- if isEOF(r) {
- l.emit(itemEOF)
- return nil
- }
- return lexBeforeValue
- }
- // lexBeforeValue scans the delimiter between key and value.
- // Leading and trailing whitespace is ignored.
- // We expect to be just after the key.
- func lexBeforeValue(l *lexer) stateFn {
- l.acceptRun(whitespace)
- l.accept(":=")
- l.acceptRun(whitespace)
- l.ignore()
- return lexValue
- }
- // lexValue scans text until the end of the line. We expect to be just after the delimiter.
- func lexValue(l *lexer) stateFn {
- for {
- switch r := l.next(); {
- case isEscape(r):
- if isEOL(l.peek()) {
- l.next()
- l.acceptRun(whitespace)
- } else {
- err := l.scanEscapeSequence()
- if err != nil {
- return l.errorf(err.Error())
- }
- }
- case isEOL(r):
- l.emit(itemValue)
- l.ignore()
- return lexBeforeKey
- case isEOF(r):
- l.emit(itemValue)
- l.emit(itemEOF)
- return nil
- default:
- l.appendRune(r)
- }
- }
- }
- // scanEscapeSequence scans either one of the escaped characters
- // or a unicode literal. We expect to be after the escape character.
- func (l *lexer) scanEscapeSequence() error {
- switch r := l.next(); {
- case isEscapedCharacter(r):
- l.appendRune(decodeEscapedCharacter(r))
- return nil
- case atUnicodeLiteral(r):
- return l.scanUnicodeLiteral()
- case isEOF(r):
- return fmt.Errorf("premature EOF")
- // silently drop the escape character and append the rune as is
- default:
- l.appendRune(r)
- return nil
- }
- }
- // scans a unicode literal in the form \uXXXX. We expect to be after the \u.
- func (l *lexer) scanUnicodeLiteral() error {
- // scan the digits
- d := make([]rune, 4)
- for i := 0; i < 4; i++ {
- d[i] = l.next()
- if d[i] == eof || !strings.ContainsRune("0123456789abcdefABCDEF", d[i]) {
- return fmt.Errorf("invalid unicode literal")
- }
- }
- // decode the digits into a rune
- r, err := strconv.ParseInt(string(d), 16, 0)
- if err != nil {
- return err
- }
- l.appendRune(rune(r))
- return nil
- }
- // decodeEscapedCharacter returns the unescaped rune. We expect to be after the escape character.
- func decodeEscapedCharacter(r rune) rune {
- switch r {
- case 'f':
- return '\f'
- case 'n':
- return '\n'
- case 'r':
- return '\r'
- case 't':
- return '\t'
- default:
- return r
- }
- }
- // atUnicodeLiteral reports whether we are at a unicode literal.
- // The escape character has already been consumed.
- func atUnicodeLiteral(r rune) bool {
- return r == 'u'
- }
- // isComment reports whether we are at the start of a comment.
- func isComment(r rune) bool {
- return r == '#' || r == '!'
- }
- // isEndOfKey reports whether the rune terminates the current key.
- func isEndOfKey(r rune) bool {
- return strings.ContainsRune(" \f\t\r\n:=", r)
- }
- // isEOF reports whether we are at EOF.
- func isEOF(r rune) bool {
- return r == eof
- }
- // isEOL reports whether we are at a new line character.
- func isEOL(r rune) bool {
- return r == '\n' || r == '\r'
- }
- // isEscape reports whether the rune is the escape character which
- // prefixes unicode literals and other escaped characters.
- func isEscape(r rune) bool {
- return r == '\\'
- }
- // isEscapedCharacter reports whether we are at one of the characters that need escaping.
- // The escape character has already been consumed.
- func isEscapedCharacter(r rune) bool {
- return strings.ContainsRune(" :=fnrt", r)
- }
- // isWhitespace reports whether the rune is a whitespace character.
- func isWhitespace(r rune) bool {
- return strings.ContainsRune(whitespace, r)
- }
|