mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-31 22:41:53 +02:00
@@ -111,31 +111,36 @@ type stateFn func(*lexer) stateFn
|
||||
|
||||
// lexer holds the state of the scanner.
|
||||
type lexer struct {
|
||||
name string // the name of the input; used only for error reports
|
||||
input string // the string being scanned
|
||||
leftDelim string // start of action
|
||||
rightDelim string // end of action
|
||||
emitComment bool // emit itemComment tokens.
|
||||
pos Pos // current position in the input
|
||||
start Pos // start position of this item
|
||||
width Pos // width of last rune read from input
|
||||
items chan item // channel of scanned items
|
||||
parenDepth int // nesting depth of ( ) exprs
|
||||
line int // 1+number of newlines seen
|
||||
startLine int // start line of this item
|
||||
breakOK bool // break keyword allowed
|
||||
continueOK bool // continue keyword allowed
|
||||
name string // the name of the input; used only for error reports
|
||||
input string // the string being scanned
|
||||
leftDelim string // start of action marker
|
||||
rightDelim string // end of action marker
|
||||
pos Pos // current position in the input
|
||||
start Pos // start position of this item
|
||||
atEOF bool // we have hit the end of input and returned eof
|
||||
parenDepth int // nesting depth of ( ) exprs
|
||||
line int // 1+number of newlines seen
|
||||
startLine int // start line of this item
|
||||
item item // item to return to parser
|
||||
insideAction bool // are we inside an action?
|
||||
options lexOptions
|
||||
}
|
||||
|
||||
// lexOptions control behavior of the lexer. All default to false.
|
||||
type lexOptions struct {
|
||||
emitComment bool // emit itemComment tokens.
|
||||
breakOK bool // break keyword allowed
|
||||
continueOK bool // continue keyword allowed
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
l.atEOF = true
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = Pos(w)
|
||||
l.pos += l.width
|
||||
l.pos += Pos(w)
|
||||
if r == '\n' {
|
||||
l.line++
|
||||
}
|
||||
@@ -149,23 +154,41 @@ func (l *lexer) peek() rune {
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
// backup steps back one rune.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
// Correct newline count.
|
||||
if l.width == 1 && l.input[l.pos] == '\n' {
|
||||
l.line--
|
||||
if !l.atEOF && l.pos > 0 {
|
||||
r, w := utf8.DecodeLastRuneInString(l.input[:l.pos])
|
||||
l.pos -= Pos(w)
|
||||
// Correct newline count.
|
||||
if r == '\n' {
|
||||
l.line--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t itemType) {
|
||||
l.items <- item{t, l.start, l.input[l.start:l.pos], l.startLine}
|
||||
// thisItem returns the item at the current input point with the specified type
|
||||
// and advances the input.
|
||||
func (l *lexer) thisItem(t itemType) item {
|
||||
i := item{t, l.start, l.input[l.start:l.pos], l.startLine}
|
||||
l.start = l.pos
|
||||
l.startLine = l.line
|
||||
return i
|
||||
}
|
||||
|
||||
// emit passes the trailing text as an item back to the parser.
|
||||
func (l *lexer) emit(t itemType) stateFn {
|
||||
return l.emitItem(l.thisItem(t))
|
||||
}
|
||||
|
||||
// emitItem passes the specified item to the parser.
|
||||
func (l *lexer) emitItem(i item) stateFn {
|
||||
l.item = i
|
||||
return nil
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
// It tracks newlines in the ignored text, so use it only
|
||||
// for text that is skipped without calling l.next.
|
||||
func (l *lexer) ignore() {
|
||||
l.line += strings.Count(l.input[l.start:l.pos], "\n")
|
||||
l.start = l.pos
|
||||
@@ -191,25 +214,31 @@ func (l *lexer) acceptRun(valid string) {
|
||||
// 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 ...any) stateFn {
|
||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.startLine}
|
||||
l.item = item{itemError, l.start, fmt.Sprintf(format, args...), l.startLine}
|
||||
l.start = 0
|
||||
l.pos = 0
|
||||
l.input = l.input[:0]
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextItem returns the next item from the input.
|
||||
// Called by the parser, not in the lexing goroutine.
|
||||
func (l *lexer) nextItem() item {
|
||||
return <-l.items
|
||||
}
|
||||
|
||||
// drain drains the output so the lexing goroutine will exit.
|
||||
// Called by the parser, not in the lexing goroutine.
|
||||
func (l *lexer) drain() {
|
||||
for range l.items {
|
||||
l.item = item{itemEOF, l.pos, "EOF", l.startLine}
|
||||
state := lexText
|
||||
if l.insideAction {
|
||||
state = lexInsideAction
|
||||
}
|
||||
for {
|
||||
state = state(l)
|
||||
if state == nil {
|
||||
return l.item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(name, input, left, right string, emitComment bool) *lexer {
|
||||
func lex(name, input, left, right string) *lexer {
|
||||
if left == "" {
|
||||
left = leftDelim
|
||||
}
|
||||
@@ -217,27 +246,17 @@ func lex(name, input, left, right string, emitComment bool) *lexer {
|
||||
right = rightDelim
|
||||
}
|
||||
l := &lexer{
|
||||
name: name,
|
||||
input: input,
|
||||
leftDelim: left,
|
||||
rightDelim: right,
|
||||
emitComment: emitComment,
|
||||
items: make(chan item),
|
||||
line: 1,
|
||||
startLine: 1,
|
||||
name: name,
|
||||
input: input,
|
||||
leftDelim: left,
|
||||
rightDelim: right,
|
||||
line: 1,
|
||||
startLine: 1,
|
||||
insideAction: false,
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for state := lexText; state != nil; {
|
||||
state = state(l)
|
||||
}
|
||||
close(l.items)
|
||||
}
|
||||
|
||||
// state functions
|
||||
|
||||
const (
|
||||
@@ -249,31 +268,33 @@ const (
|
||||
|
||||
// lexText scans until an opening action delimiter, "{{".
|
||||
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 {
|
||||
if x > 0 {
|
||||
l.pos += Pos(x)
|
||||
// Do we trim any trailing space?
|
||||
trimLength := Pos(0)
|
||||
delimEnd := l.pos + Pos(len(l.leftDelim))
|
||||
if hasLeftTrimMarker(l.input[delimEnd:]) {
|
||||
trimLength = rightTrimLength(l.input[l.start:l.pos])
|
||||
}
|
||||
l.pos -= trimLength
|
||||
l.line += strings.Count(l.input[l.start:l.pos], "\n")
|
||||
l.emit(itemText)
|
||||
i := l.thisItem(itemText)
|
||||
l.pos += trimLength
|
||||
l.ignore()
|
||||
if len(i.val) > 0 {
|
||||
return l.emitItem(i)
|
||||
}
|
||||
}
|
||||
l.pos += trimLength
|
||||
l.ignore()
|
||||
return lexLeftDelim
|
||||
}
|
||||
l.pos = Pos(len(l.input))
|
||||
// Correctly reached EOF.
|
||||
if l.pos > l.start {
|
||||
l.line += strings.Count(l.input[l.start:l.pos], "\n")
|
||||
l.emit(itemText)
|
||||
return l.emit(itemText)
|
||||
}
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
return l.emit(itemEOF)
|
||||
}
|
||||
|
||||
// rightTrimLength returns the length of the spaces at the end of the string.
|
||||
@@ -298,6 +319,7 @@ func leftTrimLength(s string) Pos {
|
||||
}
|
||||
|
||||
// lexLeftDelim scans the left delimiter, which is known to be present, possibly with a trim marker.
|
||||
// (The text to be trimmed has already been emitted.)
|
||||
func lexLeftDelim(l *lexer) stateFn {
|
||||
l.pos += Pos(len(l.leftDelim))
|
||||
trimSpace := hasLeftTrimMarker(l.input[l.pos:])
|
||||
@@ -310,28 +332,27 @@ func lexLeftDelim(l *lexer) stateFn {
|
||||
l.ignore()
|
||||
return lexComment
|
||||
}
|
||||
l.emit(itemLeftDelim)
|
||||
i := l.thisItem(itemLeftDelim)
|
||||
l.insideAction = true
|
||||
l.pos += afterMarker
|
||||
l.ignore()
|
||||
l.parenDepth = 0
|
||||
return lexInsideAction
|
||||
return l.emitItem(i)
|
||||
}
|
||||
|
||||
// lexComment scans a comment. The left comment marker is known to be present.
|
||||
func lexComment(l *lexer) stateFn {
|
||||
l.pos += Pos(len(leftComment))
|
||||
i := strings.Index(l.input[l.pos:], rightComment)
|
||||
if i < 0 {
|
||||
x := strings.Index(l.input[l.pos:], rightComment)
|
||||
if x < 0 {
|
||||
return l.errorf("unclosed comment")
|
||||
}
|
||||
l.pos += Pos(i + len(rightComment))
|
||||
l.pos += Pos(x + len(rightComment))
|
||||
delim, trimSpace := l.atRightDelim()
|
||||
if !delim {
|
||||
return l.errorf("comment ends before closing delimiter")
|
||||
}
|
||||
if l.emitComment {
|
||||
l.emit(itemComment)
|
||||
}
|
||||
i := l.thisItem(itemComment)
|
||||
if trimSpace {
|
||||
l.pos += trimMarkerLen
|
||||
}
|
||||
@@ -340,23 +361,27 @@ func lexComment(l *lexer) stateFn {
|
||||
l.pos += leftTrimLength(l.input[l.pos:])
|
||||
}
|
||||
l.ignore()
|
||||
if l.options.emitComment {
|
||||
return l.emitItem(i)
|
||||
}
|
||||
return lexText
|
||||
}
|
||||
|
||||
// lexRightDelim scans the right delimiter, which is known to be present, possibly with a trim marker.
|
||||
func lexRightDelim(l *lexer) stateFn {
|
||||
trimSpace := hasRightTrimMarker(l.input[l.pos:])
|
||||
_, trimSpace := l.atRightDelim()
|
||||
if trimSpace {
|
||||
l.pos += trimMarkerLen
|
||||
l.ignore()
|
||||
}
|
||||
l.pos += Pos(len(l.rightDelim))
|
||||
l.emit(itemRightDelim)
|
||||
i := l.thisItem(itemRightDelim)
|
||||
if trimSpace {
|
||||
l.pos += leftTrimLength(l.input[l.pos:])
|
||||
l.ignore()
|
||||
}
|
||||
return lexText
|
||||
l.insideAction = false
|
||||
return l.emitItem(i)
|
||||
}
|
||||
|
||||
// lexInsideAction scans the elements inside action delimiters.
|
||||
@@ -378,14 +403,14 @@ func lexInsideAction(l *lexer) stateFn {
|
||||
l.backup() // Put space back in case we have " -}}".
|
||||
return lexSpace
|
||||
case r == '=':
|
||||
l.emit(itemAssign)
|
||||
return l.emit(itemAssign)
|
||||
case r == ':':
|
||||
if l.next() != '=' {
|
||||
return l.errorf("expected :=")
|
||||
}
|
||||
l.emit(itemDeclare)
|
||||
return l.emit(itemDeclare)
|
||||
case r == '|':
|
||||
l.emit(itemPipe)
|
||||
return l.emit(itemPipe)
|
||||
case r == '"':
|
||||
return lexQuote
|
||||
case r == '`':
|
||||
@@ -410,20 +435,19 @@ func lexInsideAction(l *lexer) stateFn {
|
||||
l.backup()
|
||||
return lexIdentifier
|
||||
case r == '(':
|
||||
l.emit(itemLeftParen)
|
||||
l.parenDepth++
|
||||
return l.emit(itemLeftParen)
|
||||
case r == ')':
|
||||
l.emit(itemRightParen)
|
||||
l.parenDepth--
|
||||
if l.parenDepth < 0 {
|
||||
return l.errorf("unexpected right paren %#U", r)
|
||||
return l.errorf("unexpected right paren")
|
||||
}
|
||||
return l.emit(itemRightParen)
|
||||
case r <= unicode.MaxASCII && unicode.IsPrint(r):
|
||||
l.emit(itemChar)
|
||||
return l.emit(itemChar)
|
||||
default:
|
||||
return l.errorf("unrecognized character in action: %#U", r)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexSpace scans a run of space characters.
|
||||
@@ -448,13 +472,11 @@ func lexSpace(l *lexer) stateFn {
|
||||
return lexRightDelim // On the delim, so go right to that.
|
||||
}
|
||||
}
|
||||
l.emit(itemSpace)
|
||||
return lexInsideAction
|
||||
return l.emit(itemSpace)
|
||||
}
|
||||
|
||||
// lexIdentifier scans an alphanumeric.
|
||||
func lexIdentifier(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case isAlphaNumeric(r):
|
||||
@@ -468,22 +490,19 @@ Loop:
|
||||
switch {
|
||||
case key[word] > itemKeyword:
|
||||
item := key[word]
|
||||
if item == itemBreak && !l.breakOK || item == itemContinue && !l.continueOK {
|
||||
l.emit(itemIdentifier)
|
||||
} else {
|
||||
l.emit(item)
|
||||
if item == itemBreak && !l.options.breakOK || item == itemContinue && !l.options.continueOK {
|
||||
return l.emit(itemIdentifier)
|
||||
}
|
||||
return l.emit(item)
|
||||
case word[0] == '.':
|
||||
l.emit(itemField)
|
||||
return l.emit(itemField)
|
||||
case word == "true", word == "false":
|
||||
l.emit(itemBool)
|
||||
return l.emit(itemBool)
|
||||
default:
|
||||
l.emit(itemIdentifier)
|
||||
return l.emit(itemIdentifier)
|
||||
}
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexField scans a field: .Alphanumeric.
|
||||
@@ -496,8 +515,7 @@ func lexField(l *lexer) stateFn {
|
||||
// The $ has been scanned.
|
||||
func lexVariable(l *lexer) stateFn {
|
||||
if l.atTerminator() { // Nothing interesting follows -> "$".
|
||||
l.emit(itemVariable)
|
||||
return lexInsideAction
|
||||
return l.emit(itemVariable)
|
||||
}
|
||||
return lexFieldOrVariable(l, itemVariable)
|
||||
}
|
||||
@@ -507,11 +525,9 @@ func lexVariable(l *lexer) stateFn {
|
||||
func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
|
||||
if l.atTerminator() { // Nothing interesting follows -> "." or "$".
|
||||
if typ == itemVariable {
|
||||
l.emit(itemVariable)
|
||||
} else {
|
||||
l.emit(itemDot)
|
||||
return l.emit(itemVariable)
|
||||
}
|
||||
return lexInsideAction
|
||||
return l.emit(itemDot)
|
||||
}
|
||||
var r rune
|
||||
for {
|
||||
@@ -524,8 +540,7 @@ func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
|
||||
if !l.atTerminator() {
|
||||
return l.errorf("bad character %#U", r)
|
||||
}
|
||||
l.emit(typ)
|
||||
return lexInsideAction
|
||||
return l.emit(typ)
|
||||
}
|
||||
|
||||
// atTerminator reports whether the input is at valid termination character to
|
||||
@@ -541,13 +556,7 @@ func (l *lexer) atTerminator() bool {
|
||||
case eof, '.', ',', '|', ':', ')', '(':
|
||||
return true
|
||||
}
|
||||
// Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
|
||||
// succeed but should fail) but only in extremely rare cases caused by willfully
|
||||
// bad choice of delimiter.
|
||||
if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return strings.HasPrefix(l.input[l.pos:], l.rightDelim)
|
||||
}
|
||||
|
||||
// lexChar scans a character constant. The initial quote is already
|
||||
@@ -567,8 +576,7 @@ Loop:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemCharConstant)
|
||||
return lexInsideAction
|
||||
return l.emit(itemCharConstant)
|
||||
}
|
||||
|
||||
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||
@@ -584,11 +592,9 @@ func lexNumber(l *lexer) stateFn {
|
||||
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 l.emit(itemComplex)
|
||||
}
|
||||
return lexInsideAction
|
||||
return l.emit(itemNumber)
|
||||
}
|
||||
|
||||
func (l *lexer) scanNumber() bool {
|
||||
@@ -644,8 +650,7 @@ Loop:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemString)
|
||||
return lexInsideAction
|
||||
return l.emit(itemString)
|
||||
}
|
||||
|
||||
// lexRawQuote scans a raw quoted string.
|
||||
@@ -659,8 +664,7 @@ Loop:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemRawString)
|
||||
return lexInsideAction
|
||||
return l.emit(itemRawString)
|
||||
}
|
||||
|
||||
// isSpace reports whether r is a space character.
|
||||
|
@@ -362,8 +362,7 @@ var lexTests = []lexTest{
|
||||
{"extra right paren", "{{3)}}", []item{
|
||||
tLeft,
|
||||
mkItem(itemNumber, "3"),
|
||||
tRpar,
|
||||
mkItem(itemError, `unexpected right paren U+0029 ')'`),
|
||||
mkItem(itemError, "unexpected right paren"),
|
||||
}},
|
||||
|
||||
// Fixed bugs
|
||||
@@ -397,7 +396,12 @@ var lexTests = []lexTest{
|
||||
|
||||
// collect gathers the emitted items into a slice.
|
||||
func collect(t *lexTest, left, right string) (items []item) {
|
||||
l := lex(t.name, t.input, left, right, true)
|
||||
l := lex(t.name, t.input, left, right)
|
||||
l.options = lexOptions{
|
||||
emitComment: true,
|
||||
breakOK: true,
|
||||
continueOK: true,
|
||||
}
|
||||
for {
|
||||
item := l.nextItem()
|
||||
items = append(items, item)
|
||||
@@ -434,7 +438,9 @@ func TestLex(t *testing.T) {
|
||||
items := collect(&test, "", "")
|
||||
if !equal(items, test.items, false) {
|
||||
t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items)
|
||||
return // TODO
|
||||
}
|
||||
t.Log(test.name, "OK")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,6 +478,39 @@ func TestDelims(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelimsAlphaNumeric(t *testing.T) {
|
||||
test := lexTest{"right delimiter with alphanumeric start", "{{hub .host hub}}", []item{
|
||||
mkItem(itemLeftDelim, "{{hub"),
|
||||
mkItem(itemSpace, " "),
|
||||
mkItem(itemField, ".host"),
|
||||
mkItem(itemSpace, " "),
|
||||
mkItem(itemRightDelim, "hub}}"),
|
||||
tEOF,
|
||||
}}
|
||||
items := collect(&test, "{{hub", "hub}}")
|
||||
|
||||
if !equal(items, test.items, false) {
|
||||
t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelimsAndMarkers(t *testing.T) {
|
||||
test := lexTest{"delims that look like markers", "{{- .x -}} {{- - .x - -}}", []item{
|
||||
mkItem(itemLeftDelim, "{{- "),
|
||||
mkItem(itemField, ".x"),
|
||||
mkItem(itemRightDelim, " -}}"),
|
||||
mkItem(itemLeftDelim, "{{- "),
|
||||
mkItem(itemField, ".x"),
|
||||
mkItem(itemRightDelim, " -}}"),
|
||||
tEOF,
|
||||
}}
|
||||
items := collect(&test, "{{- ", " -}}")
|
||||
|
||||
if !equal(items, test.items, false) {
|
||||
t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
|
||||
}
|
||||
}
|
||||
|
||||
var lexPosTests = []lexTest{
|
||||
{"empty", "", []item{{itemEOF, 0, "", 1}}},
|
||||
{"punctuation", "{{,@%#}}", []item{
|
||||
@@ -533,22 +572,6 @@ func TestPos(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test that an error shuts down the lexing goroutine.
|
||||
func TestShutdown(t *testing.T) {
|
||||
// We need to duplicate template.Parse here to hold on to the lexer.
|
||||
const text = "erroneous{{define}}{{else}}1234"
|
||||
lexer := lex("foo", text, "{{", "}}", false)
|
||||
_, err := New("root").parseLexer(lexer)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
// The error should have drained the input. Therefore, the lexer should be shut down.
|
||||
token, ok := <-lexer.items
|
||||
if ok {
|
||||
t.Fatalf("input was not drained; got %v", token)
|
||||
}
|
||||
}
|
||||
|
||||
// parseLexer is a local version of parse that lets us pass in the lexer instead of building it.
|
||||
// We expect an error, so the tree set and funcs list are explicitly nil.
|
||||
func (t *Tree) parseLexer(lex *lexer) (tree *Tree, err error) {
|
||||
|
@@ -210,7 +210,6 @@ func (t *Tree) recover(errp *error) {
|
||||
panic(e)
|
||||
}
|
||||
if t != nil {
|
||||
t.lex.drain()
|
||||
t.stopParse()
|
||||
}
|
||||
*errp = e.(error)
|
||||
@@ -224,8 +223,6 @@ func (t *Tree) startParse(funcs []map[string]any, lex *lexer, treeSet map[string
|
||||
t.vars = []string{"$"}
|
||||
t.funcs = funcs
|
||||
t.treeSet = treeSet
|
||||
lex.breakOK = !t.hasFunction("break")
|
||||
lex.continueOK = !t.hasFunction("continue")
|
||||
}
|
||||
|
||||
// stopParse terminates parsing.
|
||||
@@ -243,8 +240,13 @@ func (t *Tree) stopParse() {
|
||||
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]any) (tree *Tree, err error) {
|
||||
defer t.recover(&err)
|
||||
t.ParseName = t.Name
|
||||
emitComment := t.Mode&ParseComments != 0
|
||||
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim, emitComment), treeSet)
|
||||
lexer := lex(t.Name, text, leftDelim, rightDelim)
|
||||
lexer.options = lexOptions{
|
||||
emitComment: t.Mode&ParseComments != 0,
|
||||
breakOK: !t.hasFunction("break"),
|
||||
continueOK: !t.hasFunction("continue"),
|
||||
}
|
||||
t.startParse(funcs, lexer, treeSet)
|
||||
t.text = text
|
||||
t.parse()
|
||||
t.add()
|
||||
@@ -341,7 +343,9 @@ func (t *Tree) parseDefinition() {
|
||||
}
|
||||
|
||||
// itemList:
|
||||
//
|
||||
// textOrAction*
|
||||
//
|
||||
// Terminates at {{end}} or {{else}}, returned separately.
|
||||
func (t *Tree) itemList() (list *ListNode, next Node) {
|
||||
list = t.newList(t.peekNonSpace().pos)
|
||||
@@ -358,6 +362,7 @@ func (t *Tree) itemList() (list *ListNode, next Node) {
|
||||
}
|
||||
|
||||
// textOrAction:
|
||||
//
|
||||
// text | comment | action
|
||||
func (t *Tree) textOrAction() Node {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
@@ -380,8 +385,10 @@ func (t *Tree) clearActionLine() {
|
||||
}
|
||||
|
||||
// Action:
|
||||
//
|
||||
// control
|
||||
// command ("|" command)*
|
||||
//
|
||||
// Left delim is past. Now get actions.
|
||||
// First word could be a keyword such as range.
|
||||
func (t *Tree) action() (n Node) {
|
||||
@@ -412,7 +419,9 @@ func (t *Tree) action() (n Node) {
|
||||
}
|
||||
|
||||
// Break:
|
||||
//
|
||||
// {{break}}
|
||||
//
|
||||
// Break keyword is past.
|
||||
func (t *Tree) breakControl(pos Pos, line int) Node {
|
||||
if token := t.nextNonSpace(); token.typ != itemRightDelim {
|
||||
@@ -425,7 +434,9 @@ func (t *Tree) breakControl(pos Pos, line int) Node {
|
||||
}
|
||||
|
||||
// Continue:
|
||||
//
|
||||
// {{continue}}
|
||||
//
|
||||
// Continue keyword is past.
|
||||
func (t *Tree) continueControl(pos Pos, line int) Node {
|
||||
if token := t.nextNonSpace(); token.typ != itemRightDelim {
|
||||
@@ -438,6 +449,7 @@ func (t *Tree) continueControl(pos Pos, line int) Node {
|
||||
}
|
||||
|
||||
// Pipeline:
|
||||
//
|
||||
// declarations? command ('|' command)*
|
||||
func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) {
|
||||
token := t.peekNonSpace()
|
||||
@@ -549,16 +561,20 @@ func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int
|
||||
}
|
||||
|
||||
// If:
|
||||
//
|
||||
// {{if pipeline}} itemList {{end}}
|
||||
// {{if pipeline}} itemList {{else}} itemList {{end}}
|
||||
//
|
||||
// If keyword is past.
|
||||
func (t *Tree) ifControl() Node {
|
||||
return t.newIf(t.parseControl(true, "if"))
|
||||
}
|
||||
|
||||
// Range:
|
||||
//
|
||||
// {{range pipeline}} itemList {{end}}
|
||||
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
||||
//
|
||||
// Range keyword is past.
|
||||
func (t *Tree) rangeControl() Node {
|
||||
r := t.newRange(t.parseControl(false, "range"))
|
||||
@@ -566,22 +582,28 @@ func (t *Tree) rangeControl() Node {
|
||||
}
|
||||
|
||||
// With:
|
||||
//
|
||||
// {{with pipeline}} itemList {{end}}
|
||||
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
||||
//
|
||||
// If keyword is past.
|
||||
func (t *Tree) withControl() Node {
|
||||
return t.newWith(t.parseControl(false, "with"))
|
||||
}
|
||||
|
||||
// End:
|
||||
//
|
||||
// {{end}}
|
||||
//
|
||||
// End keyword is past.
|
||||
func (t *Tree) endControl() Node {
|
||||
return t.newEnd(t.expect(itemRightDelim, "end").pos)
|
||||
}
|
||||
|
||||
// Else:
|
||||
//
|
||||
// {{else}}
|
||||
//
|
||||
// Else keyword is past.
|
||||
func (t *Tree) elseControl() Node {
|
||||
// Special case for "else if".
|
||||
@@ -595,7 +617,9 @@ func (t *Tree) elseControl() Node {
|
||||
}
|
||||
|
||||
// Block:
|
||||
//
|
||||
// {{block stringValue pipeline}}
|
||||
//
|
||||
// Block keyword is past.
|
||||
// The name must be something that can evaluate to a string.
|
||||
// The pipeline is mandatory.
|
||||
@@ -623,7 +647,9 @@ func (t *Tree) blockControl() Node {
|
||||
}
|
||||
|
||||
// Template:
|
||||
//
|
||||
// {{template stringValue pipeline}}
|
||||
//
|
||||
// Template keyword is past. The name must be something that can evaluate
|
||||
// to a string.
|
||||
func (t *Tree) templateControl() Node {
|
||||
@@ -654,7 +680,9 @@ func (t *Tree) parseTemplateName(token item, context string) (name string) {
|
||||
}
|
||||
|
||||
// command:
|
||||
//
|
||||
// operand (space operand)*
|
||||
//
|
||||
// space-separated arguments up to a pipeline character or right delimiter.
|
||||
// we consume the pipe character but leave the right delim to terminate the action.
|
||||
func (t *Tree) command() *CommandNode {
|
||||
@@ -684,7 +712,9 @@ func (t *Tree) command() *CommandNode {
|
||||
}
|
||||
|
||||
// operand:
|
||||
//
|
||||
// term .Field*
|
||||
//
|
||||
// An operand is a space-separated component of a command,
|
||||
// a term possibly followed by field accesses.
|
||||
// A nil return means the next item is not an operand.
|
||||
@@ -718,12 +748,14 @@ func (t *Tree) operand() Node {
|
||||
}
|
||||
|
||||
// term:
|
||||
//
|
||||
// literal (number, string, nil, boolean)
|
||||
// function (identifier)
|
||||
// .
|
||||
// .Field
|
||||
// $
|
||||
// '(' pipeline ')'
|
||||
//
|
||||
// A term is a simple "expression".
|
||||
// A nil return means the next item is not a term.
|
||||
func (t *Tree) term() Node {
|
||||
|
@@ -492,7 +492,7 @@ var errorTests = []parseTest{
|
||||
hasError, `unclosed left paren`},
|
||||
{"rparen",
|
||||
"{{.X 1 2 3 ) }}",
|
||||
hasError, `unexpected ")" in command`},
|
||||
hasError, "unexpected right paren"},
|
||||
{"rparen2",
|
||||
"{{(.X 1 2 3",
|
||||
hasError, `unclosed action`},
|
||||
@@ -600,7 +600,8 @@ func TestBlock(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLineNum(t *testing.T) {
|
||||
const count = 100
|
||||
// const count = 100
|
||||
const count = 3
|
||||
text := strings.Repeat("{{printf 1234}}\n", count)
|
||||
tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
|
||||
if err != nil {
|
||||
@@ -614,11 +615,11 @@ func TestLineNum(t *testing.T) {
|
||||
// Action first.
|
||||
action := nodes[i].(*ActionNode)
|
||||
if action.Line != line {
|
||||
t.Fatalf("line %d: action is line %d", line, action.Line)
|
||||
t.Errorf("line %d: action is line %d", line, action.Line)
|
||||
}
|
||||
pipe := action.Pipe
|
||||
if pipe.Line != line {
|
||||
t.Fatalf("line %d: pipe is line %d", line, pipe.Line)
|
||||
t.Errorf("line %d: pipe is line %d", line, pipe.Line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user