package parse
import (
)
type item struct {
typ itemType
pos Pos
val string
line int
}
func ( item) () string {
switch {
case .typ == itemEOF:
return "EOF"
case .typ == itemError:
return .val
case .typ > itemKeyword:
return fmt.Sprintf("<%s>", .val)
case len(.val) > 10:
return fmt.Sprintf("%.10q...", .val)
}
return fmt.Sprintf("%q", .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
itemBreak
itemContinue
itemDot
itemDefine
itemElse
itemEnd
itemIf
itemNil
itemRange
itemTemplate
itemWith
)
var key = map[string]itemType{
".": itemDot,
"block": itemBlock,
"break": itemBreak,
"continue": itemContinue,
"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
breakOK bool
continueOK bool
}
func ( *lexer) () rune {
if int(.pos) >= len(.input) {
.width = 0
return eof
}
, := utf8.DecodeRuneInString(.input[.pos:])
.width = Pos()
.pos += .width
if == '\n' {
.line++
}
return
}
func ( *lexer) () rune {
:= .next()
.backup()
return
}
func ( *lexer) () {
.pos -= .width
if .width == 1 && .input[.pos] == '\n' {
.line--
}
}
func ( *lexer) ( itemType) {
.items <- item{, .start, .input[.start:.pos], .startLine}
.start = .pos
.startLine = .line
}
func ( *lexer) () {
.line += strings.Count(.input[.start:.pos], "\n")
.start = .pos
.startLine = .line
}
func ( *lexer) ( string) bool {
if strings.ContainsRune(, .next()) {
return true
}
.backup()
return false
}
func ( *lexer) ( string) {
for strings.ContainsRune(, .next()) {
}
.backup()
}
func ( *lexer) ( string, ...any) stateFn {
.items <- item{itemError, .start, fmt.Sprintf(, ...), .startLine}
return nil
}
func ( *lexer) () item {
return <-.items
}
func ( *lexer) () {
for range .items {
}
}
func (, , , string, bool) *lexer {
if == "" {
= leftDelim
}
if == "" {
= rightDelim
}
:= &lexer{
name: ,
input: ,
leftDelim: ,
rightDelim: ,
emitComment: ,
items: make(chan item),
line: 1,
startLine: 1,
}
go .run()
return
}
func ( *lexer) () {
for := lexText; != nil; {
= ()
}
close(.items)
}
const (
leftDelim = "{{"
rightDelim = "}}"
leftComment = "/*"
rightComment = "*/"
)
func ( *lexer) stateFn {
.width = 0
if := strings.Index(.input[.pos:], .leftDelim); >= 0 {
:= Pos(len(.leftDelim))
.pos += Pos()
:= Pos(0)
if hasLeftTrimMarker(.input[.pos+:]) {
= rightTrimLength(.input[.start:.pos])
}
.pos -=
if .pos > .start {
.line += strings.Count(.input[.start:.pos], "\n")
.emit(itemText)
}
.pos +=
.ignore()
return lexLeftDelim
}
.pos = Pos(len(.input))
if .pos > .start {
.line += strings.Count(.input[.start:.pos], "\n")
.emit(itemText)
}
.emit(itemEOF)
return nil
}
func ( string) Pos {
return Pos(len() - len(strings.TrimRight(, spaceChars)))
}
func ( *lexer) () (, bool) {
if hasRightTrimMarker(.input[.pos:]) && strings.HasPrefix(.input[.pos+trimMarkerLen:], .rightDelim) {
return true, true
}
if strings.HasPrefix(.input[.pos:], .rightDelim) {
return true, false
}
return false, false
}
func ( string) Pos {
return Pos(len() - len(strings.TrimLeft(, spaceChars)))
}
func ( *lexer) stateFn {
.pos += Pos(len(.leftDelim))
:= hasLeftTrimMarker(.input[.pos:])
:= Pos(0)
if {
= trimMarkerLen
}
if strings.HasPrefix(.input[.pos+:], leftComment) {
.pos +=
.ignore()
return lexComment
}
.emit(itemLeftDelim)
.pos +=
.ignore()
.parenDepth = 0
return lexInsideAction
}
func ( *lexer) stateFn {
.pos += Pos(len(leftComment))
:= strings.Index(.input[.pos:], rightComment)
if < 0 {
return .errorf("unclosed comment")
}
.pos += Pos( + len(rightComment))
, := .atRightDelim()
if ! {
return .errorf("comment ends before closing delimiter")
}
if .emitComment {
.emit(itemComment)
}
if {
.pos += trimMarkerLen
}
.pos += Pos(len(.rightDelim))
if {
.pos += leftTrimLength(.input[.pos:])
}
.ignore()
return lexText
}
func ( *lexer) stateFn {
:= hasRightTrimMarker(.input[.pos:])
if {
.pos += trimMarkerLen
.ignore()
}
.pos += Pos(len(.rightDelim))
.emit(itemRightDelim)
if {
.pos += leftTrimLength(.input[.pos:])
.ignore()
}
return lexText
}
func ( *lexer) stateFn {
, := .atRightDelim()
if {
if .parenDepth == 0 {
return lexRightDelim
}
return .errorf("unclosed left paren")
}
switch := .next(); {
case == eof:
return .errorf("unclosed action")
case isSpace():
.backup()
return lexSpace
case == '=':
.emit(itemAssign)
case == ':':
if .next() != '=' {
return .errorf("expected :=")
}
.emit(itemDeclare)
case == '|':
.emit(itemPipe)
case == '"':
return lexQuote
case == '`':
return lexRawQuote
case == '$':
return lexVariable
case == '\'':
return lexChar
case == '.':
if .pos < Pos(len(.input)) {
:= .input[.pos]
if < '0' || '9' < {
return lexField
}
}
fallthrough
case == '+' || == '-' || ('0' <= && <= '9'):
.backup()
return lexNumber
case isAlphaNumeric():
.backup()
return lexIdentifier
case == '(':
.emit(itemLeftParen)
.parenDepth++
case == ')':
.emit(itemRightParen)
.parenDepth--
if .parenDepth < 0 {
return .errorf("unexpected right paren %#U", )
}
case <= unicode.MaxASCII && unicode.IsPrint():
.emit(itemChar)
default:
return .errorf("unrecognized character in action: %#U", )
}
return
}
func ( *lexer) stateFn {
var rune
var int
for {
= .peek()
if !isSpace() {
break
}
.next()
++
}
if hasRightTrimMarker(.input[.pos-1:]) && strings.HasPrefix(.input[.pos-1+trimMarkerLen:], .rightDelim) {
.backup()
if == 1 {
return lexRightDelim
}
}
.emit(itemSpace)
return lexInsideAction
}
func ( *lexer) stateFn {
:
for {
switch := .next(); {
case isAlphaNumeric():
default:
.backup()
:= .input[.start:.pos]
if !.atTerminator() {
return .errorf("bad character %#U", )
}
switch {
case key[] > itemKeyword:
:= key[]
if == itemBreak && !.breakOK || == itemContinue && !.continueOK {
.emit(itemIdentifier)
} else {
.emit()
}
case [0] == '.':
.emit(itemField)
case == "true", == "false":
.emit(itemBool)
default:
.emit(itemIdentifier)
}
break
}
}
return lexInsideAction
}
func ( *lexer) stateFn {
return lexFieldOrVariable(, itemField)
}
func ( *lexer) stateFn {
if .atTerminator() {
.emit(itemVariable)
return lexInsideAction
}
return lexFieldOrVariable(, itemVariable)
}
func ( *lexer, itemType) stateFn {
if .atTerminator() {
if == itemVariable {
.emit(itemVariable)
} else {
.emit(itemDot)
}
return lexInsideAction
}
var rune
for {
= .next()
if !isAlphaNumeric() {
.backup()
break
}
}
if !.atTerminator() {
return .errorf("bad character %#U", )
}
.emit()
return lexInsideAction
}
func ( *lexer) () bool {
:= .peek()
if isSpace() {
return true
}
switch {
case eof, '.', ',', '|', ':', ')', '(':
return true
}
if , := utf8.DecodeRuneInString(.rightDelim); == {
return true
}
return false
}
func ( *lexer) stateFn {
:
for {
switch .next() {
case '\\':
if := .next(); != eof && != '\n' {
break
}
fallthrough
case eof, '\n':
return .errorf("unterminated character constant")
case '\'':
break
}
}
.emit(itemCharConstant)
return lexInsideAction
}
func ( *lexer) stateFn {
if !.scanNumber() {
return .errorf("bad number syntax: %q", .input[.start:.pos])
}
if := .peek(); == '+' || == '-' {
if !.scanNumber() || .input[.pos-1] != 'i' {
return .errorf("bad number syntax: %q", .input[.start:.pos])
}
.emit(itemComplex)
} else {
.emit(itemNumber)
}
return lexInsideAction
}
func ( *lexer) () bool {
.accept("+-")
:= "0123456789_"
if .accept("0") {
if .accept("xX") {
= "0123456789abcdefABCDEF_"
} else if .accept("oO") {
= "01234567_"
} else if .accept("bB") {
= "01_"
}
}
.acceptRun()
if .accept(".") {
.acceptRun()
}
if len() == 10+1 && .accept("eE") {
.accept("+-")
.acceptRun("0123456789_")
}
if len() == 16+6+1 && .accept("pP") {
.accept("+-")
.acceptRun("0123456789_")
}
.accept("i")
if isAlphaNumeric(.peek()) {
.next()
return false
}
return true
}
func ( *lexer) stateFn {
:
for {
switch .next() {
case '\\':
if := .next(); != eof && != '\n' {
break
}
fallthrough
case eof, '\n':
return .errorf("unterminated quoted string")
case '"':
break
}
}
.emit(itemString)
return lexInsideAction
}
func ( *lexer) stateFn {
:
for {
switch .next() {
case eof:
return .errorf("unterminated raw quoted string")
case '`':
break
}
}
.emit(itemRawString)
return lexInsideAction
}
func ( rune) bool {
return == ' ' || == '\t' || == '\r' || == '\n'
}
func ( rune) bool {
return == '_' || unicode.IsLetter() || unicode.IsDigit()
}
func ( string) bool {
return len() >= 2 && [0] == trimMarker && isSpace(rune([1]))
}
func ( string) bool {
return len() >= 2 && isSpace(rune([0])) && [1] == trimMarker
}