package parse
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
type item struct {
typ itemType
pos Pos
val string
line int
}
func (i item ) String () string {
switch {
case i .typ == itemEOF :
return "EOF"
case i .typ == itemError :
return i .val
case i .typ > itemKeyword :
return fmt .Sprintf ("<%s>" , i .val )
case len (i .val ) > 10 :
return fmt .Sprintf ("%.10q..." , i .val )
}
return fmt .Sprintf ("%q" , i .val )
}
type itemType int
const (
itemError itemType = iota
itemBool
itemChar
itemCharConstant
itemComment
itemComplex
itemAssign
itemDeclare
itemEOF
itemField
itemIdentifier
itemLeftDelim
itemLeftParen
itemNumber
itemPipe
itemRawString
itemRightDelim
itemRightParen
itemSpace
itemString
itemText
itemVariable
itemKeyword
itemBlock
itemDot
itemDefine
itemElse
itemEnd
itemIf
itemNil
itemRange
itemTemplate
itemWith
)
var key = map [string ]itemType {
"." : itemDot ,
"block" : itemBlock ,
"define" : itemDefine ,
"else" : itemElse ,
"end" : itemEnd ,
"if" : itemIf ,
"range" : itemRange ,
"nil" : itemNil ,
"template" : itemTemplate ,
"with" : itemWith ,
}
const eof = -1
const (
spaceChars = " \t\r\n"
trimMarker = '-'
trimMarkerLen = Pos (1 + 1 )
)
type stateFn func (*lexer ) stateFn
type lexer struct {
name string
input string
leftDelim string
rightDelim string
emitComment bool
pos Pos
start Pos
width Pos
items chan item
parenDepth int
line int
startLine int
}
func (l *lexer ) next () rune {
if int (l .pos ) >= len (l .input ) {
l .width = 0
return eof
}
r , w := utf8 .DecodeRuneInString (l .input [l .pos :])
l .width = Pos (w )
l .pos += l .width
if r == '\n' {
l .line ++
}
return r
}
func (l *lexer ) peek () rune {
r := l .next ()
l .backup ()
return r
}
func (l *lexer ) backup () {
l .pos -= l .width
if l .width == 1 && l .input [l .pos ] == '\n' {
l .line --
}
}
func (l *lexer ) emit (t itemType ) {
l .items <- item {t , l .start , l .input [l .start :l .pos ], l .startLine }
l .start = l .pos
l .startLine = l .line
}
func (l *lexer ) ignore () {
l .line += strings .Count (l .input [l .start :l .pos ], "\n" )
l .start = l .pos
l .startLine = l .line
}
func (l *lexer ) accept (valid string ) bool {
if strings .ContainsRune (valid , l .next ()) {
return true
}
l .backup ()
return false
}
func (l *lexer ) acceptRun (valid string ) {
for strings .ContainsRune (valid , l .next ()) {
}
l .backup ()
}
func (l *lexer ) errorf (format string , args ...interface {}) stateFn {
l .items <- item {itemError , l .start , fmt .Sprintf (format , args ...), l .startLine }
return nil
}
func (l *lexer ) nextItem () item {
return <-l .items
}
func (l *lexer ) drain () {
for range l .items {
}
}
func lex (name , input , left , right string , emitComment bool ) *lexer {
if left == "" {
left = leftDelim
}
if right == "" {
right = rightDelim
}
l := &lexer {
name : name ,
input : input ,
leftDelim : left ,
rightDelim : right ,
emitComment : emitComment ,
items : make (chan item ),
line : 1 ,
startLine : 1 ,
}
go l .run ()
return l
}
func (l *lexer ) run () {
for state := lexText ; state != nil ; {
state = state (l )
}
close (l .items )
}
const (
leftDelim = "{{"
rightDelim = "}}"
leftComment = "/*"
rightComment = "*/"
)
func lexText (l *lexer ) stateFn {
l .width = 0
if x := strings .Index (l .input [l .pos :], l .leftDelim ); x >= 0 {
ldn := Pos (len (l .leftDelim ))
l .pos += Pos (x )
trimLength := Pos (0 )
if hasLeftTrimMarker (l .input [l .pos +ldn :]) {
trimLength = rightTrimLength (l .input [l .start :l .pos ])
}
l .pos -= trimLength
if l .pos > l .start {
l .line += strings .Count (l .input [l .start :l .pos ], "\n" )
l .emit (itemText )
}
l .pos += trimLength
l .ignore ()
return lexLeftDelim
}
l .pos = Pos (len (l .input ))
if l .pos > l .start {
l .line += strings .Count (l .input [l .start :l .pos ], "\n" )
l .emit (itemText )
}
l .emit (itemEOF )
return nil
}
func rightTrimLength (s string ) Pos {
return Pos (len (s ) - len (strings .TrimRight (s , spaceChars )))
}
func (l *lexer ) atRightDelim () (delim , trimSpaces bool ) {
if hasRightTrimMarker (l .input [l .pos :]) && strings .HasPrefix (l .input [l .pos +trimMarkerLen :], l .rightDelim ) {
return true , true
}
if strings .HasPrefix (l .input [l .pos :], l .rightDelim ) {
return true , false
}
return false , false
}
func leftTrimLength (s string ) Pos {
return Pos (len (s ) - len (strings .TrimLeft (s , spaceChars )))
}
func lexLeftDelim (l *lexer ) stateFn {
l .pos += Pos (len (l .leftDelim ))
trimSpace := hasLeftTrimMarker (l .input [l .pos :])
afterMarker := Pos (0 )
if trimSpace {
afterMarker = trimMarkerLen
}
if strings .HasPrefix (l .input [l .pos +afterMarker :], leftComment ) {
l .pos += afterMarker
l .ignore ()
return lexComment
}
l .emit (itemLeftDelim )
l .pos += afterMarker
l .ignore ()
l .parenDepth = 0
return lexInsideAction
}
func lexComment (l *lexer ) stateFn {
l .pos += Pos (len (leftComment ))
i := strings .Index (l .input [l .pos :], rightComment )
if i < 0 {
return l .errorf ("unclosed comment" )
}
l .pos += Pos (i + len (rightComment ))
delim , trimSpace := l .atRightDelim ()
if !delim {
return l .errorf ("comment ends before closing delimiter" )
}
if l .emitComment {
l .emit (itemComment )
}
if trimSpace {
l .pos += trimMarkerLen
}
l .pos += Pos (len (l .rightDelim ))
if trimSpace {
l .pos += leftTrimLength (l .input [l .pos :])
}
l .ignore ()
return lexText
}
func lexRightDelim (l *lexer ) stateFn {
trimSpace := hasRightTrimMarker (l .input [l .pos :])
if trimSpace {
l .pos += trimMarkerLen
l .ignore ()
}
l .pos += Pos (len (l .rightDelim ))
l .emit (itemRightDelim )
if trimSpace {
l .pos += leftTrimLength (l .input [l .pos :])
l .ignore ()
}
return lexText
}
func lexInsideAction (l *lexer ) stateFn {
delim , _ := l .atRightDelim ()
if delim {
if l .parenDepth == 0 {
return lexRightDelim
}
return l .errorf ("unclosed left paren" )
}
switch r := l .next (); {
case r == eof :
return l .errorf ("unclosed action" )
case isSpace (r ):
l .backup ()
return lexSpace
case r == '=' :
l .emit (itemAssign )
case r == ':' :
if l .next () != '=' {
return l .errorf ("expected :=" )
}
l .emit (itemDeclare )
case r == '|' :
l .emit (itemPipe )
case r == '"' :
return lexQuote
case r == '`' :
return lexRawQuote
case r == '$' :
return lexVariable
case r == '\'' :
return lexChar
case r == '.' :
if l .pos < Pos (len (l .input )) {
r := l .input [l .pos ]
if r < '0' || '9' < r {
return lexField
}
}
fallthrough
case r == '+' || r == '-' || ('0' <= r && r <= '9' ):
l .backup ()
return lexNumber
case isAlphaNumeric (r ):
l .backup ()
return lexIdentifier
case r == '(' :
l .emit (itemLeftParen )
l .parenDepth ++
case r == ')' :
l .emit (itemRightParen )
l .parenDepth --
if l .parenDepth < 0 {
return l .errorf ("unexpected right paren %#U" , r )
}
case r <= unicode .MaxASCII && unicode .IsPrint (r ):
l .emit (itemChar )
default :
return l .errorf ("unrecognized character in action: %#U" , r )
}
return lexInsideAction
}
func lexSpace (l *lexer ) stateFn {
var r rune
var numSpaces int
for {
r = l .peek ()
if !isSpace (r ) {
break
}
l .next ()
numSpaces ++
}
if hasRightTrimMarker (l .input [l .pos -1 :]) && strings .HasPrefix (l .input [l .pos -1 +trimMarkerLen :], l .rightDelim ) {
l .backup ()
if numSpaces == 1 {
return lexRightDelim
}
}
l .emit (itemSpace )
return lexInsideAction
}
func lexIdentifier (l *lexer ) stateFn {
Loop :
for {
switch r := l .next (); {
case isAlphaNumeric (r ):
default :
l .backup ()
word := l .input [l .start :l .pos ]
if !l .atTerminator () {
return l .errorf ("bad character %#U" , r )
}
switch {
case key [word ] > itemKeyword :
l .emit (key [word ])
case word [0 ] == '.' :
l .emit (itemField )
case word == "true" , word == "false" :
l .emit (itemBool )
default :
l .emit (itemIdentifier )
}
break Loop
}
}
return lexInsideAction
}
func lexField (l *lexer ) stateFn {
return lexFieldOrVariable (l , itemField )
}
func lexVariable (l *lexer ) stateFn {
if l .atTerminator () {
l .emit (itemVariable )
return lexInsideAction
}
return lexFieldOrVariable (l , itemVariable )
}
func lexFieldOrVariable (l *lexer , typ itemType ) stateFn {
if l .atTerminator () {
if typ == itemVariable {
l .emit (itemVariable )
} else {
l .emit (itemDot )
}
return lexInsideAction
}
var r rune
for {
r = l .next ()
if !isAlphaNumeric (r ) {
l .backup ()
break
}
}
if !l .atTerminator () {
return l .errorf ("bad character %#U" , r )
}
l .emit (typ )
return lexInsideAction
}
func (l *lexer ) atTerminator () bool {
r := l .peek ()
if isSpace (r ) {
return true
}
switch r {
case eof , '.' , ',' , '|' , ':' , ')' , '(' :
return true
}
if rd , _ := utf8 .DecodeRuneInString (l .rightDelim ); rd == r {
return true
}
return false
}
func lexChar (l *lexer ) stateFn {
Loop :
for {
switch l .next () {
case '\\' :
if r := l .next (); r != eof && r != '\n' {
break
}
fallthrough
case eof , '\n' :
return l .errorf ("unterminated character constant" )
case '\'' :
break Loop
}
}
l .emit (itemCharConstant )
return lexInsideAction
}
func lexNumber (l *lexer ) stateFn {
if !l .scanNumber () {
return l .errorf ("bad number syntax: %q" , l .input [l .start :l .pos ])
}
if sign := l .peek (); sign == '+' || sign == '-' {
if !l .scanNumber () || l .input [l .pos -1 ] != 'i' {
return l .errorf ("bad number syntax: %q" , l .input [l .start :l .pos ])
}
l .emit (itemComplex )
} else {
l .emit (itemNumber )
}
return lexInsideAction
}
func (l *lexer ) scanNumber () bool {
l .accept ("+-" )
digits := "0123456789_"
if l .accept ("0" ) {
if l .accept ("xX" ) {
digits = "0123456789abcdefABCDEF_"
} else if l .accept ("oO" ) {
digits = "01234567_"
} else if l .accept ("bB" ) {
digits = "01_"
}
}
l .acceptRun (digits )
if l .accept ("." ) {
l .acceptRun (digits )
}
if len (digits ) == 10 +1 && l .accept ("eE" ) {
l .accept ("+-" )
l .acceptRun ("0123456789_" )
}
if len (digits ) == 16 +6 +1 && l .accept ("pP" ) {
l .accept ("+-" )
l .acceptRun ("0123456789_" )
}
l .accept ("i" )
if isAlphaNumeric (l .peek ()) {
l .next ()
return false
}
return true
}
func lexQuote (l *lexer ) stateFn {
Loop :
for {
switch l .next () {
case '\\' :
if r := l .next (); r != eof && r != '\n' {
break
}
fallthrough
case eof , '\n' :
return l .errorf ("unterminated quoted string" )
case '"' :
break Loop
}
}
l .emit (itemString )
return lexInsideAction
}
func lexRawQuote (l *lexer ) stateFn {
Loop :
for {
switch l .next () {
case eof :
return l .errorf ("unterminated raw quoted string" )
case '`' :
break Loop
}
}
l .emit (itemRawString )
return lexInsideAction
}
func isSpace (r rune ) bool {
return r == ' ' || r == '\t' || r == '\r' || r == '\n'
}
func isAlphaNumeric (r rune ) bool {
return r == '_' || unicode .IsLetter (r ) || unicode .IsDigit (r )
}
func hasLeftTrimMarker (s string ) bool {
return len (s ) >= 2 && s [0 ] == trimMarker && isSpace (rune (s [1 ]))
}
func hasRightTrimMarker (s string ) bool {
return len (s ) >= 2 && isSpace (rune (s [0 ])) && s [1 ] == trimMarker
}
The pages are generated with Golds v0.3.6 . (GOOS=darwin GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .