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
pos Pos
start Pos
atEOF bool
parenDepth int
line int
startLine int
item item
insideAction bool
options lexOptions
}
type lexOptions struct {
emitComment bool
breakOK bool
continueOK bool
}
func ( *lexer) () rune {
if int(.pos) >= len(.input) {
.atEOF = true
return eof
}
, := utf8.DecodeRuneInString(.input[.pos:])
.pos += Pos()
if == '\n' {
.line++
}
return
}
func ( *lexer) () rune {
:= .next()
.backup()
return
}
func ( *lexer) () {
if !.atEOF && .pos > 0 {
, := utf8.DecodeLastRuneInString(.input[:.pos])
.pos -= Pos()
if == '\n' {
.line--
}
}
}
func ( *lexer) ( itemType) item {
:= item{, .start, .input[.start:.pos], .startLine}
.start = .pos
.startLine = .line
return
}
func ( *lexer) ( itemType) stateFn {
return .emitItem(.thisItem())
}
func ( *lexer) ( item) stateFn {
.item =
return nil
}
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 {
.item = item{itemError, .start, fmt.Sprintf(, ...), .startLine}
.start = 0
.pos = 0
.input = .input[:0]
return nil
}
func ( *lexer) () item {
.item = item{itemEOF, .pos, "EOF", .startLine}
:= lexText
if .insideAction {
= lexInsideAction
}
for {
= ()
if == nil {
return .item
}
}
}
func (, , , string) *lexer {
if == "" {
= leftDelim
}
if == "" {
= rightDelim
}
:= &lexer{
name: ,
input: ,
leftDelim: ,
rightDelim: ,
line: 1,
startLine: 1,
insideAction: false,
}
return
}
const (
leftDelim = "{{"
rightDelim = "}}"
leftComment = "/*"
rightComment = "*/"
)
func ( *lexer) stateFn {
if := strings.Index(.input[.pos:], .leftDelim); >= 0 {
if > 0 {
.pos += Pos()
:= Pos(0)
:= .pos + Pos(len(.leftDelim))
if hasLeftTrimMarker(.input[:]) {
= rightTrimLength(.input[.start:.pos])
}
.pos -=
.line += strings.Count(.input[.start:.pos], "\n")
:= .thisItem(itemText)
.pos +=
.ignore()
if len(.val) > 0 {
return .emitItem()
}
}
return lexLeftDelim
}
.pos = Pos(len(.input))
if .pos > .start {
.line += strings.Count(.input[.start:.pos], "\n")
return .emit(itemText)
}
return .emit(itemEOF)
}
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
}
:= .thisItem(itemLeftDelim)
.insideAction = true
.pos +=
.ignore()
.parenDepth = 0
return .emitItem()
}
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")
}
.line += strings.Count(.input[.start:.pos], "\n")
:= .thisItem(itemComment)
if {
.pos += trimMarkerLen
}
.pos += Pos(len(.rightDelim))
if {
.pos += leftTrimLength(.input[.pos:])
}
.ignore()
if .options.emitComment {
return .emitItem()
}
return lexText
}
func ( *lexer) stateFn {
, := .atRightDelim()
if {
.pos += trimMarkerLen
.ignore()
}
.pos += Pos(len(.rightDelim))
:= .thisItem(itemRightDelim)
if {
.pos += leftTrimLength(.input[.pos:])
.ignore()
}
.insideAction = false
return .emitItem()
}
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 == '=':
return .emit(itemAssign)
case == ':':
if .next() != '=' {
return .errorf("expected :=")
}
return .emit(itemDeclare)
case == '|':
return .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 == '(':
.parenDepth++
return .emit(itemLeftParen)
case == ')':
.parenDepth--
if .parenDepth < 0 {
return .errorf("unexpected right paren")
}
return .emit(itemRightParen)
case <= unicode.MaxASCII && unicode.IsPrint():
return .emit(itemChar)
default:
return .errorf("unrecognized character in action: %#U", )
}
}
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
}
}
return .emit(itemSpace)
}
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 && !.options.breakOK || == itemContinue && !.options.continueOK {
return .emit(itemIdentifier)
}
return .emit()
case [0] == '.':
return .emit(itemField)
case == "true", == "false":
return .emit(itemBool)
default:
return .emit(itemIdentifier)
}
}
}
}
func ( *lexer) stateFn {
return lexFieldOrVariable(, itemField)
}
func ( *lexer) stateFn {
if .atTerminator() {
return .emit(itemVariable)
}
return lexFieldOrVariable(, itemVariable)
}
func ( *lexer, itemType) stateFn {
if .atTerminator() {
if == itemVariable {
return .emit(itemVariable)
}
return .emit(itemDot)
}
var rune
for {
= .next()
if !isAlphaNumeric() {
.backup()
break
}
}
if !.atTerminator() {
return .errorf("bad character %#U", )
}
return .emit()
}
func ( *lexer) () bool {
:= .peek()
if isSpace() {
return true
}
switch {
case eof, '.', ',', '|', ':', ')', '(':
return true
}
return strings.HasPrefix(.input[.pos:], .rightDelim)
}
func ( *lexer) stateFn {
:
for {
switch .next() {
case '\\':
if := .next(); != eof && != '\n' {
break
}
fallthrough
case eof, '\n':
return .errorf("unterminated character constant")
case '\'':
break
}
}
return .emit(itemCharConstant)
}
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])
}
return .emit(itemComplex)
}
return .emit(itemNumber)
}
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
}
}
return .emit(itemString)
}
func ( *lexer) stateFn {
:
for {
switch .next() {
case eof:
return .errorf("unterminated raw quoted string")
case '`':
break
}
}
return .emit(itemRawString)
}
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
}