Go Template Toolkit, Сергей Свистунов, Lazada
-
Upload
mailru-group -
Category
Software
-
view
6.260 -
download
1
Transcript of Go Template Toolkit, Сергей Свистунов, Lazada
Go template toolkit
github.com/go-qbit/template
Пример шаблона
[% WRAPPER page(caption string) %]
<!DOCTYPE html>
<html>
<head>
<title>[% caption | html %]</title>
</head>
<body>
[% CONTENT %]
</body>
</html>
[% END %]
[% TEMPLATE Test(header string, users []User) USE WRAPPER page(header) %]
<h1>[% header | html %]</h1>
<hr>
[% FOR user IN users %]
<p>
[% PROCESS UserName(user) %]:
[% user.Age %] ([% IF user.IsMan %]Man[% ELSE %]Woman[% END %])
</p>
[% END %]
[% END %]
[% TEMPLATE UserName(user User) %]
[% user.Name | html +%] [% user.Lastname | html %]
[% END %]
package templates
import (
"context"
"github.com/go-qbit/template/filter"
"io"
)
var (
s1ef997d5dd161505e230e6dbbfcaaa48 = []byte{0x3C, 0x21, 0x44, 0x4F, 0x43, 0x54, 0x59, 0x50, 0x45,
sf3847a1d6d502a855dd746c6ccc81aae = []byte{0x3C, 0x2F, 0x62, 0x6F, 0x64, 0x79, 0x3E, 0xA, 0x3C, 0x2F
s89efc060410cc373c7da167f1451d1f5 = []byte{0x3C, 0x2F, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x3E, 0xA, 0x20
)
func Wrapperpage(ctx context.Context, w io.Writer, tplClbF func(), caption string) {
w.Write(s1ef997d5dd161505e230e6dbbfcaaa48)
io.WriteString(w, filter.Filterhtml(caption))
w.Write(s89efc060410cc373c7da167f1451d1f5)
tplClbF()
w.Write(sf3847a1d6d502a855dd746c6ccc81aae)
}
...
func ProcessTest(ctx context.Context, w io.Writer, header string, users []User) {
Wrapperpage(ctx, w, func() {
w.Write(sedbe56ac7404d66602a627bd637ef2c1)
io.WriteString(w, filter.Filterhtml(header))
w.Write(s0f9baed348e2ded156c71ed2555fb1da)
for _, user := range users {
w.Write(sf715636fb0391a8fdd43473a43b1ea72)
ProcessUserName(ctx, w, user)
w.Write(sf8de4bf55e29f51642893aea2c376d30)
io.WriteString(w, utils.ToString(user.Age))
w.Write(sd2c38510206af487d1a4984a8af4ca49)
if user.IsMan {
w.Write(s9444e86b936ba53fdc56632949cb3038)
} else {
w.Write(s9ed6dfeeb854d5f5e292f977637a729b)
}
w.Write(s9e23173c90723882045e02a5a6c79a9b)
}
}, header)
}
...
Отличия
• Perl TT MACRO -> TEMPLATE / WRAPPER
• Perl TT USE -> IMPORT
• Явная передача данных, нет stash’а
• Нет виртуальных методов для переменных
• Код генерируется до компиляции (ttgen)
Benchmark
BenchmarkGoCoreTemplate-4 3000 5306959 ns/op 1041697 B/op 31035 allocs/op
BenchmarkQBitTemplate-4 30000 407632 ns/op 36096 B/op 4006 allocs/op
Синтаксис
• Директивы должны быть внутри [% %]
• Несколько директив должны разделяться ;
• Пробельные символы вокруг [% %] не печатаются, чтобы их сохранить надо использовать [%+ для сохранения слева и +%]для сохранения справа.
• Поддерживаются фильтры: [% expr | filter1 | filter2 %]
Директивы и операции
• IMPORT
• TEMPLATE
• WRAPPER
• PROCESS
• CONTENT
• IF / ELSE
• FOR … IN slice/map
• FOR ; ; ;
• Сравнение• >, GT
• <, LT
• >=, GE
• <=, LE
• ==, EQ
• Математические• +
• -
• *
• /
• %
• Boolean• &&, AND
• ||, OR
• Унарные• !, NOT
• ++
• --
• Присвоение• :=
• =
Инструменты
Средства разработки анализаторов• ANTLR — генератор парсеров
• Bison — генератор парсеров
• Coco/R — генератор сканера и парсера
• GOLD — парсер
• JavaCC — генератор парсеров для языка Java
• Lemon Parser — генератор парсеров
• Lex — генератор сканеров
• Ragel — генератор встраиваемых парсеров
• Spirit Parser Framework — генератор парсеров
• SYNTAX
• Syntax Definition Formalism
• UltraGram
• VivaCore
• Yacc — генератор парсеров
Средства разработки анализаторов• ANTLR — генератор парсеров
• Bison — генератор парсеров
• Coco/R — генератор сканера и парсера
• GOLD — парсер
• JavaCC — генератор парсеров для языка Java
• Lemon Parser — генератор парсеров
• Lex — генератор сканеров
• Ragel — генератор встраиваемых парсеров
• Spirit Parser Framework — генератор парсеров
• SYNTAX
• Syntax Definition Formalism
• UltraGram
• VivaCore
• Yacc — генератор парсеров
Препроцессинг
[% WRAPPER page(caption string) %]
<!DOCTYPE html>
<html>
<head>
<title>[% caption | html %]</title>
</head>
<body>
[% CONTENT %]
</body>
</html>
[% END %]
[% WRAPPER page(caption string) %]
<!DOCTYPE html>
<html>
<head>
<title>[% caption | html %]</title>
</head>
<body>
[% CONTENT %]
</body>
</html>
[% END %]
WRAPPER page(caption string);
"<!DOCTYPE html>
<html>
<head>
<title>"; caption | html; "</title>
</head>
<body>";
CONTENT;
"</body>
</html>";
END;
Построение ASTgo tool yacc
Грамматика для YACC
%{
package template;
import (
__yyfmt__ "fmt"
)
%}
%token IDENTIFIER STRING NUMBER FOR IN IF ELSE END VARS TEMPLATE IMPORT WRAPPER USE
%token CONTENT_MARKER PROCESS EQ NE GE LE OR AND NOT ASSIGNMENT INC DEC
%union {
string string
iAstNode iAstNode
astList *astList
...
}
%type <string> STRING IDENTIFIER NUMBER var_type var_value
%type <iAstNode> top file header macros_stmt var body_stmt expr loop
%type <astList> imports import_list macroses param_list var_list body
...
%left '+' '-' '*' '/' '%' '>' '<' EQ NE GE LE OR AND
%left NOT
%%
top: file { yylex.(*exprLex).result = $$ }
file: header macroses { $$ = &astFile{$1, $2} }
...
macroses: macros_stmt { $$ = &astList{[]iAstNode{$1}} }
| macroses ';' macros_stmt { $$.Add($3) }
macros_stmt: { $$ = nil }
| TEMPLATE IDENTIFIER '(' var_list ')' use_wrapper ';' body ';' END
{ $$ = &astTemplate{$2, $4, $6, $8} }
| WRAPPER IDENTIFIER '(' var_list ')' ';' body ';' END
{ $$ = &astWrapper{$2, $4, $7} }
| STRING { $$ = nil }
...
expr: expr '>' expr { $$ = &astExpr{">", $1, $3} }
| expr '<' expr { $$ = &astExpr{"<", $1, $3} }
| expr '+' expr { $$ = &astExpr{"+", $1, $3} }
| expr '-' expr { $$ = &astExpr{"-", $1, $3} }
| expr '*' expr { $$ = &astExpr{"*", $1, $3} }
...
| NOT expr { $$ = &astExpr{"!", nil, $2} }
| '(' expr ')' { $$ = &astParenthesis{$2} }
| var_value INC { $$ = &astExpr{"++", &astValue{$1}, nil} }
| var_value DEC { $$ = &astExpr{"--", &astValue{$1}, nil} }
| var_value '(' param_list ')'{ $$ = &astFunc{$1, $3} }
| var_value { $$ = &astValue{$1} }
| STRING { $$ = &astString{$1} }
| NUMBER { $$ = &astValue{$1} }
...
%%
y.go...
type yySymType struct {
yys int
string string
iAstNode iAstNode
...
}
const IDENTIFIER = 57346
const STRING = 57347
...
type yyLexer interface {
Lex(lval *yySymType) int
Error(s string)
}
...
func yyParse(yylex yyLexer) int {
return yyNewParser().Parse(yylex)
}
Lexer
Простые токеныvar simpleTokens = []simpleToken{
{"TEMPLATE", TEMPLATE},
{"WRAPPER", WRAPPER},
...
{"&&", AND},
{"||", OR},
{"++", INC},
{"--", DEC},
{"!", NOT},
}
Регулярные выраженияvar reTokens = []reToken{
{`^(?:\")(?:[^\\\"]*(?:\\.[^\\\"]*)*)(?:\")`, STRING},
{`^-?\d+(?:[.,]\d+)?`, NUMBER},
{`^[a-zA-Z_][a-zA-Z0-9\_]*`, IDENTIFIER},
}
Начало
Начинается с простого токена
Начинается с RegExp’а
Длина текста равна 0
Пробельный символ
Нет
Нет
Нет
Вернуть 0
Сдвинуть текст на 1 символ
Да
Да Вернуть код символа
Нет
• Заполнить yyval• Сдвинуть текст на
длину токена
Да
Да
Вернуть код токена
AST
iAstNode
type iAstNode interface {
GetImports() []string
GetStrings() []string
WriteGo(io.Writer, *GenGoOpts)
}
astFunctype astFunc struct {
name string
params *astList
}
func (n *astFunc) GetImports() []string {
if n.params != nil {
return n.params.GetImports()
}
return []string{}
}
func (n *astFunc) GetStrings() []string {
if n.params != nil {
return n.params.GetStrings()
}
return []string{}
}
astFunc
func (n *astFunc) WriteGo(w io.Writer, opts *GenGoOpts) {
io.WriteString(w, n.name+"(")
if n.params != nil {
for i, param := range n.params.children {
if i > 0 {
io.WriteString(w, ", ")
}
param.WriteGo(w, opts)
}
}
io.WriteString(w, ")")
}
ОптимизацииBenchmark & pprof
Версия №1 func ProcessTest(ctx context.Context, w io.Writer, header string, users []User) {
Wrapperpage(ctx, w, func() {
io.WriteString(w, "<h1>")
io.WriteString(w, filter.Filterhtml(header))
io.WriteString(w, "</h1>\n <hr>")
for _, user := range users {
io.WriteString(w, "<p>")
ProcessUserName(ctx, w, user)
io.WriteString(w, ":")
io.WriteString(w, fmt.Sprint(user.Age))
io.WriteString(w, "(")
if user.IsMan {
io.WriteString(w, "Man")
} else {
io.WriteString(w, "Woman")
}
io.WriteString(w, ")\n </p>")
}
}, header)
} 728133 ns/op 48149 B/op 6008 allocs/op
CPU profile
CPU profile
Версия №2func ProcessTest(ctx context.Context, w io.Writer, header string, users []User) {
Wrapperpage(ctx, w, func() {
io.WriteString(w, "<h1>")
io.WriteString(w, filter.Filterhtml(header))
io.WriteString(w, "</h1>\n <hr>")
for _, user := range users {
io.WriteString(w, "<p>")
ProcessUserName(ctx, w, user)
io.WriteString(w, ":")
io.WriteString(w, utils.ToString(user.Age))
io.WriteString(w, "(")
if user.IsMan {
io.WriteString(w, "Man")
} else {
io.WriteString(w, "Woman")
}
io.WriteString(w, ")\n </p>")
}
}, header)
} 490204 ns/op 36096 B/op 4006 allocs/op
fmt.Sprint -> utils.ToString
func ToString(v interface{}) string {
switch v := v.(type) {
case string:
return v
case int:
return strconv.FormatInt(int64(v), 10)
...
case bool:
if v {
return "true"
}
return "false"
default:
return fmt.Sprint(v)
}
}
CPU profile
src/io/io.go
// WriteString writes the contents of the string s to w, which accepts a slice of bytes.
// If w implements a WriteString method, it is invoked directly.
// Otherwise, w.Write is called exactly once.
func WriteString(w Writer, s string) (n int, err error) {
if sw, ok := w.(stringWriter); ok {
return sw.WriteString(s)
}
return w.Write([]byte(s))
}
Версия №3
var (
s3c00a1c100edee938dfcd27113fd1f93 = []byte{0x20}
sd2c38510206af487d1a4984a8af4ca49 = []byte{0x28}
s9e23173c90723882045e02a5a6c79a9b = []byte{0x29, 0xA, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x70, 0x3E}
sf8de4bf55e29f51642893aea2c376d30 = []byte{0x3A}
s0f9baed348e2ded156c71ed2555fb1da = []byte{0x3C, 0x2F, 0x68, 0x31, 0x3E, 0xA, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x68, 0x72, 0x3E}
sedbe56ac7404d66602a627bd637ef2c1 = []byte{0x3C, 0x68, 0x31, 0x3E}
sf715636fb0391a8fdd43473a43b1ea72 = []byte{0x3C, 0x70, 0x3E}
s9444e86b936ba53fdc56632949cb3038 = []byte{0x4D, 0x61, 0x6E}
s9ed6dfeeb854d5f5e292f977637a729b = []byte{0x57, 0x6F, 0x6D, 0x61, 0x6E}
)
Версия №3func ProcessTest(ctx context.Context, w io.Writer, header string, users []User) {
Wrapperpage(ctx, w, func() {
w.Write(sedbe56ac7404d66602a627bd637ef2c1)
io.WriteString(w, filter.Filterhtml(header))
w.Write(s0f9baed348e2ded156c71ed2555fb1da)
for _, user := range users {
w.Write(sf715636fb0391a8fdd43473a43b1ea72)
ProcessUserName(ctx, w, user)
w.Write(sf8de4bf55e29f51642893aea2c376d30)
io.WriteString(w, utils.ToString(user.Age))
w.Write(sd2c38510206af487d1a4984a8af4ca49)
if user.IsMan {
w.Write(s9444e86b936ba53fdc56632949cb3038)
} else {
w.Write(s9ed6dfeeb854d5f5e292f977637a729b)
}
w.Write(s9e23173c90723882045e02a5a6c79a9b)
}
}, header)
} 390227 ns/op 36096 B/op 4006 allocs/op
Результат оптимизаций
0
100000
200000
300000
400000
500000
600000
700000
800000
Версия 1 Версия 2 Версия 3
ns/op
0
1000
2000
3000
4000
5000
6000
7000
Версия 1 Версия 2 Версия 3
allocs/op
Вопросы?