mirror of
https://github.com/danog/liquid.git
synced 2024-11-30 09:48:59 +01:00
Record source line number
This commit is contained in:
parent
6075f39527
commit
08fcc4eafc
@ -15,6 +15,24 @@ type Chunk struct {
|
||||
Source string // Source is the entirety of the chunk, including the "{{", "{%", etc. markers.
|
||||
}
|
||||
|
||||
// ChunkType is the type of a Chunk
|
||||
type ChunkType int
|
||||
|
||||
const (
|
||||
// TextChunkType is the type of a text Chunk
|
||||
TextChunkType ChunkType = iota
|
||||
// TagChunkType is the type of a tag Chunk "{%…%}"
|
||||
TagChunkType
|
||||
// ObjChunkType is the type of an object Chunk "{{…}}"
|
||||
ObjChunkType
|
||||
)
|
||||
|
||||
// SourceInfo contains a Chunk's source information
|
||||
type SourceInfo struct {
|
||||
Pathname string
|
||||
lineNo int
|
||||
}
|
||||
|
||||
func (c Chunk) String() string {
|
||||
switch c.Type {
|
||||
case TextChunkType:
|
||||
@ -28,20 +46,9 @@ func (c Chunk) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// SourceInfo contains a Chunk's source information
|
||||
type SourceInfo struct {
|
||||
Pathname string
|
||||
lineNo int
|
||||
func (s SourceInfo) String() string {
|
||||
if s.Pathname != "" {
|
||||
return fmt.Sprintf("%s:%d", s.Pathname, s.lineNo)
|
||||
}
|
||||
return fmt.Sprintf("line %d", s.lineNo)
|
||||
}
|
||||
|
||||
// ChunkType is the type of a Chunk
|
||||
type ChunkType int
|
||||
|
||||
const (
|
||||
// TextChunkType is the type of a text Chunk
|
||||
TextChunkType ChunkType = iota
|
||||
// TagChunkType is the type of a tag Chunk "{%…%}"
|
||||
TagChunkType
|
||||
// ObjChunkType is the type of an object Chunk "{{…}}"
|
||||
ObjChunkType
|
||||
)
|
||||
|
@ -8,23 +8,27 @@ import (
|
||||
|
||||
// Parse creates an AST from a sequence of Chunks.
|
||||
func Parse(chunks []Chunk) (ASTNode, error) {
|
||||
// a stack of control tag state, for matching nested {%if}{%endif%} etc.
|
||||
type frame struct {
|
||||
cd *controlTagDefinition
|
||||
cn *ASTControlTag
|
||||
ap *[]ASTNode
|
||||
cd *controlTagDefinition // saved local ccd
|
||||
cn *ASTControlTag // saved local cn
|
||||
ap *[]ASTNode // saved local ap
|
||||
}
|
||||
var (
|
||||
root = &ASTSeq{}
|
||||
ap = &root.Children // pointer to current node accumulation slice
|
||||
ccd *controlTagDefinition
|
||||
ccn *ASTControlTag
|
||||
stack []frame // stack of control structures
|
||||
rawTag *ASTRaw
|
||||
root = &ASTSeq{} // root of AST; will be returned
|
||||
ap = &root.Children // newly-constructed nodes are appended here
|
||||
ccd *controlTagDefinition // current control tag definition
|
||||
ccn *ASTControlTag // current control node
|
||||
stack []frame // stack of control structures
|
||||
rawTag *ASTRaw // current raw tag
|
||||
inComment = false
|
||||
inRaw = false
|
||||
)
|
||||
for _, c := range chunks {
|
||||
switch {
|
||||
// The parser needs to know about comment and raw, because tags inside
|
||||
// needn't match each other e.g. {%comment%}{%if%}{%endcomment%}
|
||||
// TODO is this true?
|
||||
case inComment:
|
||||
if c.Type == TagChunkType && c.Name == "endcomment" {
|
||||
inComment = false
|
||||
@ -84,7 +88,7 @@ func Parse(chunks []Chunk) (ASTNode, error) {
|
||||
}
|
||||
}
|
||||
if ccd != nil {
|
||||
return nil, fmt.Errorf("unterminated %s tag", ccd.name)
|
||||
return nil, fmt.Errorf("unterminated %s tag at %s", ccd.name, ccn.SourceInfo)
|
||||
}
|
||||
if err := evaluateBuilders(root); err != nil {
|
||||
return nil, err
|
||||
|
@ -4,6 +4,7 @@ package chunks
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var chunkMatcher = regexp.MustCompile(`{{\s*(.+?)\s*}}|{%\s*(\w+)(?:\s+((?:[^%]|%[^}])+?))?\s*%}`)
|
||||
@ -13,29 +14,30 @@ func Scan(data string, pathname string) []Chunk {
|
||||
// TODO error on unterminated {{ and {%
|
||||
// TODO probably an error when a tag contains a {{ or {%, at least outside of a string
|
||||
var (
|
||||
sourceInfo = SourceInfo{pathname, 0}
|
||||
out = make([]Chunk, 0)
|
||||
p, pe = 0, len(data)
|
||||
matches = chunkMatcher.FindAllStringSubmatchIndex(data, -1)
|
||||
p, pe = 0, len(data)
|
||||
si = SourceInfo{pathname, 1}
|
||||
out = make([]Chunk, 0)
|
||||
)
|
||||
for _, m := range matches {
|
||||
for _, m := range chunkMatcher.FindAllStringSubmatchIndex(data, -1) {
|
||||
ts, te := m[0], m[1]
|
||||
if p < ts {
|
||||
out = append(out, Chunk{Type: TextChunkType, SourceInfo: sourceInfo, Source: data[p:ts]})
|
||||
out = append(out, Chunk{Type: TextChunkType, SourceInfo: si, Source: data[p:ts]})
|
||||
si.lineNo += strings.Count(data[p:ts], "\n")
|
||||
}
|
||||
source := data[ts:te]
|
||||
switch data[ts+1] {
|
||||
case '{':
|
||||
out = append(out, Chunk{
|
||||
Type: ObjChunkType,
|
||||
SourceInfo: sourceInfo,
|
||||
Source: data[ts:te],
|
||||
SourceInfo: si,
|
||||
Source: source,
|
||||
Parameters: data[m[2]:m[3]],
|
||||
})
|
||||
case '%':
|
||||
c := Chunk{
|
||||
Type: TagChunkType,
|
||||
SourceInfo: sourceInfo,
|
||||
Source: data[ts:te],
|
||||
SourceInfo: si,
|
||||
Source: source,
|
||||
Name: data[m[4]:m[5]],
|
||||
}
|
||||
if m[6] > 0 {
|
||||
@ -43,10 +45,11 @@ func Scan(data string, pathname string) []Chunk {
|
||||
}
|
||||
out = append(out, c)
|
||||
}
|
||||
si.lineNo += strings.Count(source, "\n")
|
||||
p = te
|
||||
}
|
||||
if p < pe {
|
||||
out = append(out, Chunk{Type: TextChunkType, SourceInfo: sourceInfo, Source: data[p:]})
|
||||
out = append(out, Chunk{Type: TextChunkType, SourceInfo: si, Source: data[p:]})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user