1
0
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:
Oliver Steele 2017-06-30 08:42:11 -04:00
parent 6075f39527
commit 08fcc4eafc
3 changed files with 51 additions and 37 deletions

View File

@ -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
)

View File

@ -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

View File

@ -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
}