-
Notifications
You must be signed in to change notification settings - Fork 0
/
parse.go
156 lines (138 loc) · 3.62 KB
/
parse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright 2019 Erik Lott. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
package parse
import (
"errors"
"io"
"strconv"
"strings"
"github.com/eriklott/mustache/internal/ast"
"github.com/eriklott/mustache/internal/token"
)
// Default mustache delimeters
const (
DefaultLeftDelim = "{{"
DefaultRightDelim = "}}"
)
// parser contains the state for the parsing process.
type parser struct {
name string
src string
s *token.Scanner
}
// Parse transforms a template string into a tree of nodes. If an error is
// encountered, parsing stops and the error is returned.
func Parse(name, src, leftDelim, rightDelim string) (*ast.Tree, error) {
p := &parser{
name: name,
src: src,
s: token.NewScanner(name, src, leftDelim, rightDelim),
}
tree := &ast.Tree{
Name: name,
}
err := p.parse(tree, 0)
return tree, err
}
// parentNode represents an element that can add an ast.Node (mainly a ast.Tree or ast.Section)
type parentNode interface {
Add(ast.Node)
}
// parse recursively parses the template string, constructing nodes and adding them to
// the tree. If an error is encountered, parse stops and the error is returned.
func (p *parser) parse(parent parentNode, start int) error {
for {
t, err := p.s.Next()
if err == io.EOF {
// If the eof has been reached while parsing the inside of a section, return
// the eof error to the calling function so the error can be handled there.
if _, ok := parent.(*ast.Section); ok {
return err
}
// eof reached normally. parsing is complete.
return nil
}
if err != nil {
return err
}
switch t.Type {
case token.TEXT:
parent.Add(&ast.Text{
Text: t.Text,
EndOfLine: false,
})
case token.TEXT_EOL:
parent.Add(&ast.Text{
Text: t.Text,
EndOfLine: true,
})
case token.VARIABLE:
parent.Add(&ast.Variable{
Key: splitKey(t.Text),
Unescaped: false,
Line: t.Line,
Column: t.Column,
})
case token.UNESCAPED_VARIABLE, token.UNESCAPED_VARIABLE_SYM:
parent.Add(&ast.Variable{
Key: splitKey(t.Text),
Unescaped: true,
Line: t.Line,
Column: t.Column,
})
case token.SECTION, token.INVERTED_SECTION:
node := &ast.Section{
Key: splitKey(t.Text),
Inverted: t.Type == token.INVERTED_SECTION,
LDelim: p.s.LeftDelim(),
RDelim: p.s.RightDelim(),
Line: t.Line,
Column: t.Column,
}
err := p.parse(node, t.EndOffset)
if err == io.EOF {
return p.error(t.Line, t.Column, "unclosed section tag: "+t.Text)
}
if err != nil {
return err
}
parent.Add(node)
case token.SECTION_END:
section, ok := parent.(*ast.Section)
if !ok || strings.Join(section.Key, ".") != t.Text {
return p.error(t.Line, t.Column, "unexpected section closing tag: "+t.Text)
}
section.Text = p.src[start:t.Offset]
return nil
case token.PARTIAL:
parent.Add(&ast.Partial{
Key: t.Text,
Indent: t.Indent,
Line: t.Line,
Column: t.Column,
})
}
}
}
// error returns an error message prefixed with the line and column number of
// where in the template the error occured.
func (p *parser) error(ln, col int, msg string) error {
var b strings.Builder
b.WriteString(p.name)
b.WriteString(":")
b.WriteString(strconv.Itoa(ln))
b.WriteString(":")
b.WriteString(strconv.Itoa(col))
b.WriteString(":")
b.WriteString(" ")
b.WriteString(msg)
return errors.New(b.String())
}
// splitKey splits a dotted key into a slice of keys.
func splitKey(key string) []string {
if key == "." {
return []string{"."}
}
return strings.Split(key, ".")
}