-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompiled.go
More file actions
310 lines (257 loc) · 8.44 KB
/
Copy pathcompiled.go
File metadata and controls
310 lines (257 loc) · 8.44 KB
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
package twig
import (
"bytes"
"encoding/binary"
"encoding/gob"
"fmt"
"io"
"sync"
"time"
)
// bytesBufferPool is used to reuse byte buffers during template serialization
var bytesBufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
// getBuffer gets a bytes.Buffer from the pool
func getBuffer() *bytes.Buffer {
buf := bytesBufferPool.Get().(*bytes.Buffer)
buf.Reset()
return buf
}
// putBuffer returns a bytes.Buffer to the pool
func putBuffer(buf *bytes.Buffer) {
bytesBufferPool.Put(buf)
}
func init() {
// Register all node types for serialization
// This is only needed for the AST serialization which still uses gob
gob.Register(&RootNode{})
gob.Register(&TextNode{})
gob.Register(&PrintNode{})
gob.Register(&BlockNode{})
gob.Register(&ForNode{})
gob.Register(&IfNode{})
gob.Register(&SetNode{})
gob.Register(&IncludeNode{})
gob.Register(&ExtendsNode{})
gob.Register(&MacroNode{})
gob.Register(&ImportNode{})
gob.Register(&FromImportNode{})
gob.Register(&FunctionNode{})
gob.Register(&CommentNode{})
// Expression nodes
gob.Register(&ExpressionNode{})
gob.Register(&VariableNode{})
gob.Register(&LiteralNode{})
gob.Register(&UnaryNode{})
gob.Register(&BinaryNode{})
}
// CompiledTemplate represents a compiled Twig template
type CompiledTemplate struct {
Name string // Template name
Source string // Original template source
LastModified int64 // Last modification timestamp
CompileTime int64 // Time when compilation occurred
AST []byte // Serialized AST data
}
// Size returns the approximate memory size of the compiled template in bytes
func (c *CompiledTemplate) Size() int {
if c == nil {
return 0
}
// Calculate approximate size
size := 0
size += len(c.Name)
size += len(c.Source)
size += len(c.AST)
size += 16 // Size of int64 fields
return size
}
// CompileTemplate compiles a parsed template into a compiled format
func CompileTemplate(tmpl *Template) (*CompiledTemplate, error) {
if tmpl == nil {
return nil, fmt.Errorf("%w: cannot compile nil template", ErrCompilation)
}
// Serialize the AST (Node tree)
var astBuf bytes.Buffer
enc := gob.NewEncoder(&astBuf)
// Encode the AST
if err := enc.Encode(tmpl.nodes); err != nil {
// If serialization fails, we'll still create the template but without AST
// We continue silently to allow template creation
}
// Store the template source, metadata, and AST
compiled := &CompiledTemplate{
Name: tmpl.name,
Source: tmpl.source,
LastModified: tmpl.lastModified,
CompileTime: time.Now().Unix(),
AST: astBuf.Bytes(),
}
return compiled, nil
}
// LoadFromCompiled loads a template from its compiled representation
func LoadFromCompiled(compiled *CompiledTemplate, env *Environment, engine *Engine) (*Template, error) {
if compiled == nil {
return nil, fmt.Errorf("%w: cannot load from nil compiled template", ErrCompilation)
}
var nodes Node
// Try to use the cached AST if available
if len(compiled.AST) > 0 {
// Attempt to deserialize the AST
dec := gob.NewDecoder(bytes.NewReader(compiled.AST))
// Try to decode the AST
err := dec.Decode(&nodes)
if err != nil {
// Fall back to parsing if AST deserialization fails
// We continue silently and fall back to parsing
nodes = nil
}
}
// If AST deserialization failed or AST is not available, parse the source
if nodes == nil {
parser := &Parser{}
var err error
nodes, err = parser.Parse(compiled.Source)
if err != nil {
return nil, fmt.Errorf("failed to parse compiled template: %w", err)
}
}
// Create the template from the nodes (either from AST or freshly parsed)
tmpl := &Template{
name: compiled.Name,
source: compiled.Source,
nodes: nodes,
env: env,
engine: engine,
lastModified: compiled.LastModified,
}
return tmpl, nil
}
// writeString writes a string to a buffer with length prefix
func writeString(w io.Writer, s string) error {
// Write the string length as uint32
if err := binary.Write(w, binary.LittleEndian, uint32(len(s))); err != nil {
return err
}
// Write the string data
_, err := w.Write([]byte(s))
return err
}
// readString reads a length-prefixed string from a reader
func readString(r io.Reader) (string, error) {
// Read string length
var length uint32
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
return "", err
}
// Read string data
data := make([]byte, length)
if _, err := io.ReadFull(r, data); err != nil {
return "", err
}
return string(data), nil
}
// SerializeCompiledTemplate serializes a compiled template to a byte array
func SerializeCompiledTemplate(compiled *CompiledTemplate) ([]byte, error) {
// Get a buffer from the pool
buf := getBuffer()
defer putBuffer(buf)
// Use binary encoding for metadata (more efficient than gob)
// Write format version (for future compatibility)
if err := binary.Write(buf, binary.LittleEndian, uint8(1)); err != nil {
return nil, fmt.Errorf("failed to serialize version: %w", err)
}
// Write Name as length-prefixed string
if err := writeString(buf, compiled.Name); err != nil {
return nil, fmt.Errorf("failed to serialize name: %w", err)
}
// Write Source as length-prefixed string
if err := writeString(buf, compiled.Source); err != nil {
return nil, fmt.Errorf("failed to serialize source: %w", err)
}
// Write timestamps
if err := binary.Write(buf, binary.LittleEndian, compiled.LastModified); err != nil {
return nil, fmt.Errorf("failed to serialize LastModified: %w", err)
}
if err := binary.Write(buf, binary.LittleEndian, compiled.CompileTime); err != nil {
return nil, fmt.Errorf("failed to serialize CompileTime: %w", err)
}
// Write AST data length followed by data
if err := binary.Write(buf, binary.LittleEndian, uint32(len(compiled.AST))); err != nil {
return nil, fmt.Errorf("failed to serialize AST length: %w", err)
}
if _, err := buf.Write(compiled.AST); err != nil {
return nil, fmt.Errorf("failed to serialize AST data: %w", err)
}
// Return a copy of the buffer data
return bytes.Clone(buf.Bytes()), nil
}
// DeserializeCompiledTemplate deserializes a compiled template from a byte array
func DeserializeCompiledTemplate(data []byte) (*CompiledTemplate, error) {
if len(data) == 0 {
return nil, fmt.Errorf("empty data cannot be deserialized")
}
// Try the new binary format first
compiled, err := deserializeBinaryFormat(data)
if err == nil {
return compiled, nil
}
// Fall back to the old gob format if binary deserialization fails
// This ensures backward compatibility with previously compiled templates
return deserializeGobFormat(data)
}
// deserializeBinaryFormat deserializes using the new binary format
func deserializeBinaryFormat(data []byte) (*CompiledTemplate, error) {
// Create a reader for the data
r := bytes.NewReader(data)
// Read and verify format version
var version uint8
if err := binary.Read(r, binary.LittleEndian, &version); err != nil {
return nil, fmt.Errorf("failed to read format version: %w", err)
}
if version != 1 {
return nil, fmt.Errorf("unsupported format version: %d", version)
}
// Create a new compiled template
compiled := new(CompiledTemplate)
// Read Name
var err error
compiled.Name, err = readString(r)
if err != nil {
return nil, fmt.Errorf("failed to read name: %w", err)
}
// Read Source
compiled.Source, err = readString(r)
if err != nil {
return nil, fmt.Errorf("failed to read source: %w", err)
}
// Read timestamps
if err := binary.Read(r, binary.LittleEndian, &compiled.LastModified); err != nil {
return nil, fmt.Errorf("failed to read LastModified: %w", err)
}
if err := binary.Read(r, binary.LittleEndian, &compiled.CompileTime); err != nil {
return nil, fmt.Errorf("failed to read CompileTime: %w", err)
}
// Read AST data length and data
var astLength uint32
if err := binary.Read(r, binary.LittleEndian, &astLength); err != nil {
return nil, fmt.Errorf("failed to read AST length: %w", err)
}
compiled.AST = make([]byte, astLength)
if _, err := io.ReadFull(r, compiled.AST); err != nil {
return nil, fmt.Errorf("failed to read AST data: %w", err)
}
return compiled, nil
}
// deserializeGobFormat deserializes using the old gob format
func deserializeGobFormat(data []byte) (*CompiledTemplate, error) {
dec := gob.NewDecoder(bytes.NewReader(data))
var compiled CompiledTemplate
if err := dec.Decode(&compiled); err != nil {
return nil, fmt.Errorf("failed to deserialize compiled template: %w", err)
}
return &compiled, nil
}