Skip to content

Commit

Permalink
refactor: template node parser
Browse files Browse the repository at this point in the history
Also reduces allocations and increases speed.

Before:

BenchmarkTemplParser-10                    25168             47568 ns/op           27243 B/op        747 allocs/op

After:

BenchmarkTemplParser-10                    26148             45871 ns/op           25915 B/op        716 allocs/op
  • Loading branch information
a-h committed Nov 14, 2023
1 parent 8480a71 commit cc5054c
Show file tree
Hide file tree
Showing 25 changed files with 112 additions and 204 deletions.
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.470
0.2.470
2 changes: 1 addition & 1 deletion cmd/templ/migratecmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func migrate(fileName string) (err error) {
var v2Template v2.TemplateFile

// Copy the package and any imports.
var sb strings.Builder
sb := new(strings.Builder)
sb.WriteString("package " + v1Template.Package.Expression.Value)
sb.WriteString("\n")
if len(v1Template.Imports) > 0 {
Expand Down
2 changes: 1 addition & 1 deletion parser/v1/elementparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ func TestElementParserErrors(t *testing.T) {
}

func TestBigElement(t *testing.T) {
var sb strings.Builder
sb := new(strings.Builder)
sb.WriteString("<div>")
for i := 0; i < 4096*4; i++ {
sb.WriteString("a")
Expand Down
3 changes: 2 additions & 1 deletion parser/v2/calltemplateparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ var callTemplateExpressionStart = parse.Or(parse.String("{! "), parse.String("{!

type callTemplateExpressionParser struct{}

func (p callTemplateExpressionParser) Parse(pi *parse.Input) (r CallTemplateExpression, ok bool, err error) {
func (p callTemplateExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
// Check the prefix first.
if _, ok, err = callTemplateExpressionStart.Parse(pi); err != nil || !ok {
return
}

// Once we have a prefix, we must have an expression that returns a template.
var r CallTemplateExpression
if r.Expression, ok, err = exp.Parse(pi); err != nil || !ok {
return
}
Expand Down
23 changes: 14 additions & 9 deletions parser/v2/childrenparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import (
"github.com/a-h/parse"
)

var childrenExpression = parse.Func(func(in *parse.Input) (out ChildrenExpression, ok bool, err error) {
_, ok, err = parse.StringFrom(
openBraceWithOptionalPadding,
parse.OptionalWhitespace,
parse.String("children..."),
parse.OptionalWhitespace,
closeBraceWithOptionalPadding,
).Parse(in)
return out, ok, err
var childrenExpressionParser = parse.StringFrom(
openBraceWithOptionalPadding,
parse.OptionalWhitespace,
parse.String("children..."),
parse.OptionalWhitespace,
closeBraceWithOptionalPadding,
)

var childrenExpression = parse.Func(func(in *parse.Input) (n Node, ok bool, err error) {
_, ok, err = childrenExpressionParser.Parse(in)
if err != nil || !ok {
return
}
return ChildrenExpression{}, true, nil
})
4 changes: 3 additions & 1 deletion parser/v2/cssparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,12 @@ var expressionCSSPropertyParser = parse.Func(func(pi *parse.Input) (r Expression
}

// { string }
if r.Value, ok, err = stringExpression.Parse(pi); err != nil || !ok {
var se Node
if se, ok, err = stringExpression.Parse(pi); err != nil || !ok {
pi.Seek(start)
return
}
r.Value = se.(StringExpression)

// ;
if _, ok, err = Must(parse.String(";"), "missing expected semicolon (;)").Parse(pi); err != nil || !ok {
Expand Down
3 changes: 2 additions & 1 deletion parser/v2/doctypeparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import (

var doctypeStartParser = parse.StringInsensitive("<!doctype ")

var docType = parse.Func(func(pi *parse.Input) (r DocType, ok bool, err error) {
var docType = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
var r DocType
if _, ok, err = doctypeStartParser.Parse(pi); err != nil || !ok {
return
}
Expand Down
3 changes: 2 additions & 1 deletion parser/v2/elementparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,10 @@ var element elementParser

type elementParser struct{}

func (elementParser) Parse(pi *parse.Input) (r Element, ok bool, err error) {
func (elementParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
start := pi.Position()

var r Element
if r, ok, err = parse.Any[Element](selfClosingElement, elementOpenClose).Parse(pi); err != nil || !ok {
return
}
Expand Down
2 changes: 1 addition & 1 deletion parser/v2/elementparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ func TestElementParserErrors(t *testing.T) {
}

func TestBigElement(t *testing.T) {
var sb strings.Builder
sb := new(strings.Builder)
sb.WriteString("<div>")
for i := 0; i < 4096*4; i++ {
sb.WriteString("a")
Expand Down
12 changes: 8 additions & 4 deletions parser/v2/expressionparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
)

// StripType takes the parser and throws away the return value.
func StripType[T any](p parse.Parser[T]) parse.Parser[interface{}] {
return parse.Func(func(in *parse.Input) (out interface{}, ok bool, err error) {
func StripType[T any](p parse.Parser[T]) parse.Parser[any] {
return parse.Func(func(in *parse.Input) (out any, ok bool, err error) {
return p.Parse(in)
})
}
Expand Down Expand Up @@ -57,6 +57,10 @@ var openBracket = parse.String("(")
var closeBracket = parse.String(")")
var closeBracketWithOptionalPadding = parse.StringFrom(optionalSpaces, closeBracket)

var stringUntilNewLine = parse.StringUntil[string](parse.NewLine)
var newLineOrEOF = parse.Or(parse.NewLine, parse.EOF[string]())
var stringUntilNewLineOrEOF = parse.StringUntil(newLineOrEOF)

var exp = expressionParser{
startBraceCount: 1,
}
Expand All @@ -70,7 +74,7 @@ func (p expressionParser) Parse(pi *parse.Input) (s Expression, ok bool, err err

braceCount := p.startBraceCount

var sb strings.Builder
sb := new(strings.Builder)
loop:
for {
var result string
Expand Down Expand Up @@ -147,7 +151,7 @@ func (p functionArgsParser) Parse(pi *parse.Input) (s Expression, ok bool, err e

bracketCount := p.startBracketCount

var sb strings.Builder
sb := new(strings.Builder)
loop:
for {
var result string
Expand Down
3 changes: 2 additions & 1 deletion parser/v2/forexpressionparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"github.com/a-h/parse"
)

var forExpression = parse.Func(func(pi *parse.Input) (r ForExpression, ok bool, err error) {
var forExpression = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
// Check the prefix first.
if _, ok, err = parse.String("for ").Parse(pi); err != nil || !ok {
return
Expand All @@ -13,6 +13,7 @@ var forExpression = parse.Func(func(pi *parse.Input) (r ForExpression, ok bool,
// Once we've got a prefix, read until {\n.
// If there's no match, there's no {\n, which is an error.
from := pi.Position()
var r ForExpression
until := parse.All(openBraceWithOptionalPadding, parse.NewLine)
var fexp string
if fexp, ok, err = Must(parse.StringUntil(until), "for: "+unterminatedMissingCurly).Parse(pi); err != nil || !ok {
Expand Down
8 changes: 5 additions & 3 deletions parser/v2/gocommentparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ type goSingleLineCommentParser struct {

var goSingleLineComment = goSingleLineCommentParser{}

func (p goSingleLineCommentParser) Parse(pi *parse.Input) (c GoComment, ok bool, err error) {
func (p goSingleLineCommentParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
// Comment start.
var c GoComment
if _, ok, err = goSingleLineCommentStart.Parse(pi); err != nil || !ok {
return
}
Expand All @@ -37,8 +38,9 @@ type goMultiLineCommentParser struct {

var goMultiLineComment = goMultiLineCommentParser{}

func (p goMultiLineCommentParser) Parse(pi *parse.Input) (c GoComment, ok bool, err error) {
func (p goMultiLineCommentParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
// Comment start.
var c GoComment
if _, ok, err = goMultiLineCommentStart.Parse(pi); err != nil || !ok {
return
}
Expand All @@ -55,4 +57,4 @@ func (p goMultiLineCommentParser) Parse(pi *parse.Input) (c GoComment, ok bool,
return c, true, nil
}

var goComment = parse.Any[GoComment](goSingleLineComment, goMultiLineComment)
var goComment = parse.Any[Node](goSingleLineComment, goMultiLineComment)
3 changes: 2 additions & 1 deletion parser/v2/htmlcommentparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ type htmlCommentParser struct {

var htmlComment = htmlCommentParser{}

func (p htmlCommentParser) Parse(pi *parse.Input) (c HTMLComment, ok bool, err error) {
func (p htmlCommentParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
// Comment start.
var c HTMLComment
if _, ok, err = htmlCommentStart.Parse(pi); err != nil || !ok {
return
}
Expand Down
3 changes: 2 additions & 1 deletion parser/v2/ifexpressionparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ var ifExpression ifExpressionParser

type ifExpressionParser struct{}

func (ifExpressionParser) Parse(pi *parse.Input) (r IfExpression, ok bool, err error) {
func (ifExpressionParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
// Check the prefix first.
if _, ok, err = parse.String("if ").Parse(pi); err != nil || !ok {
return
}

// Once we've got a prefix, read until {\n.
// If there's no match, there's no {\n, which is an error.
var r IfExpression
if r.Expression, ok, err = Must(ExpressionOf(parse.StringUntil(parse.All(openBraceWithOptionalPadding, parse.NewLine))), "if: "+unterminatedMissingCurly).Parse(pi); err != nil || !ok {
return
}
Expand Down
2 changes: 1 addition & 1 deletion parser/v2/packageparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var pkg = parse.Func(func(pi *parse.Input) (pkg Package, ok bool, err error) {

// Once we have the prefix, it's an expression until the end of the line.
var exp string
if exp, ok, err = Must(parse.StringUntil(parse.NewLine), "package literal not terminated").Parse(pi); err != nil || !ok {
if exp, ok, err = Must(stringUntilNewLine, "package literal not terminated").Parse(pi); err != nil || !ok {
return
}
if len(exp) == 0 {
Expand Down
3 changes: 2 additions & 1 deletion parser/v2/raw.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type rawElementParser struct {
name string
}

func (p rawElementParser) Parse(pi *parse.Input) (e RawElement, ok bool, err error) {
func (p rawElementParser) Parse(pi *parse.Input) (n Node, ok bool, err error) {
start := pi.Index()

// <
Expand All @@ -27,6 +27,7 @@ func (p rawElementParser) Parse(pi *parse.Input) (e RawElement, ok bool, err err
}

// Element name.
var e RawElement
if e.Name, ok, err = parse.String(p.name).Parse(pi); err != nil || !ok {
pi.Seek(start)
return
Expand Down
3 changes: 2 additions & 1 deletion parser/v2/stringexpressionparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"github.com/a-h/parse"
)

var stringExpression = parse.Func(func(pi *parse.Input) (r StringExpression, ok bool, err error) {
var stringExpression = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
// Check the prefix first.
if _, ok, err = parse.Or(parse.String("{ "), parse.String("{")).Parse(pi); err != nil || !ok {
return
}

// Once we have a prefix, we must have an expression that returns a string.
var r StringExpression
if r.Expression, ok, err = exp.Parse(pi); err != nil || !ok {
return
}
Expand Down
3 changes: 2 additions & 1 deletion parser/v2/stringexpressionparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ func TestStringExpressionParser(t *testing.T) {
tt := tt
t.Run(tt.name, func(t *testing.T) {
input := parse.NewInput(tt.input)
actual, ok, err := stringExpression.Parse(input)
an, ok, err := stringExpression.Parse(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ok {
t.Fatalf("unexpected failure for input %q", tt.input)
}
actual := an.(StringExpression)
if diff := cmp.Diff(tt.expected, actual); diff != "" {
t.Error(diff)
}
Expand Down
3 changes: 2 additions & 1 deletion parser/v2/switchexpressionparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import (
"github.com/a-h/parse"
)

var switchExpression = parse.Func(func(pi *parse.Input) (r SwitchExpression, ok bool, err error) {
var switchExpression = parse.Func(func(pi *parse.Input) (n Node, ok bool, err error) {
// Check the prefix first.
if _, ok, err = parse.String("switch ").Parse(pi); err != nil || !ok {
return
}

// Once we've got a prefix, read until {\n.
var r SwitchExpression
endOfStatementExpression := ExpressionOf(parse.StringUntil(parse.All(openBraceWithOptionalPadding, parse.NewLine)))
if r.Expression, ok, err = Must(endOfStatementExpression, "switch: "+unterminatedMissingCurly).Parse(pi); err != nil || !ok {
return
Expand Down
14 changes: 8 additions & 6 deletions parser/v2/templatefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,18 @@ func NewTemplateFileParser(pkg string) TemplateFileParser {
}
}

var ErrLegacyFileFormat = errors.New("Legacy file format - run templ migrate")
var ErrTemplateNotFound = errors.New("Template not found")
var ErrLegacyFileFormat = errors.New("legacy file format - run templ migrate")
var ErrTemplateNotFound = errors.New("template not found")

type TemplateFileParser struct {
DefaultPackage string
}

var legacyPackageParser = parse.String("{% package")

func (p TemplateFileParser) Parse(pi *parse.Input) (tf TemplateFile, ok bool, err error) {
// If we're parsing a legacy file, complain that migration needs to happen.
_, ok, err = parse.String("{% package").Parse(pi)
_, ok, err = legacyPackageParser.Parse(pi)
if err != nil {
return
}
Expand All @@ -91,7 +93,7 @@ func (p TemplateFileParser) Parse(pi *parse.Input) (tf TemplateFile, ok bool, er
}

var line string
line, ok, err = parse.StringUntil(parse.NewLine).Parse(pi)
line, ok, err = stringUntilNewLine.Parse(pi)
if err != nil {
return
}
Expand Down Expand Up @@ -146,14 +148,14 @@ outer:
}

// Anything that isn't template content is Go code.
var code strings.Builder
code := new(strings.Builder)
from := pi.Position()
inner:
for {
// Check to see if this line isn't Go code.
last := pi.Index()
var l string
if l, ok, err = parse.StringUntil(parse.Or(parse.NewLine, parse.EOF[string]())).Parse(pi); err != nil {
if l, ok, err = stringUntilNewLineOrEOF.Parse(pi); err != nil {
return
}
hasTemplatePrefix := strings.HasPrefix(l, "templ ") || strings.HasPrefix(l, "css ") || strings.HasPrefix(l, "script ")
Expand Down
Loading

0 comments on commit cc5054c

Please sign in to comment.