-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathswap.go
More file actions
163 lines (132 loc) · 3.36 KB
/
Copy pathswap.go
File metadata and controls
163 lines (132 loc) · 3.36 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
package swap
import (
"bytes"
"fmt"
"io"
"sync"
"unsafe"
"github.com/flothq/swap/internal/compiler"
"github.com/flothq/swap/internal/lexer"
"github.com/flothq/swap/internal/lru"
"github.com/flothq/swap/internal/vm"
"github.com/flothq/swap/pkg/bytecode"
)
type Engine struct {
cache *lru.Cache[string, *vm.Program]
engineOpts EngineOpts
}
type EngineOpts struct {
CacheSize int
CacheEnabled bool
}
type EngineOption func(*EngineOpts)
func WithCacheEnabled(enabled bool) EngineOption {
return func(opts *EngineOpts) {
opts.CacheEnabled = enabled
}
}
func WithCacheSize(size int) EngineOption {
return func(opts *EngineOpts) {
opts.CacheSize = size
}
}
func NewEngine(opts ...EngineOption) *Engine {
e := &Engine{
engineOpts: EngineOpts{},
}
for _, opt := range opts {
opt(&e.engineOpts)
}
if e.engineOpts.CacheEnabled {
if e.engineOpts.CacheSize <= 0 {
e.engineOpts.CacheSize = 1024 * 1024
}
e.cache = lru.New[string, *vm.Program](e.engineOpts.CacheSize, func(k string, v *vm.Program) int {
return len(k) + int(unsafe.Sizeof(*v))
})
}
return e
}
var programPool = sync.Pool{
New: func() interface{} {
return &vm.Program{}
},
}
func (e *Engine) Execute(template string, context map[string]interface{}) ([]byte, error) {
var program *vm.Program
if e.cache != nil {
if cached, ok := e.cache.Get(template); ok {
program = cached
}
}
if program == nil {
buf, err := e.compile(template)
if err != nil {
return nil, err
}
program, err = e.deserializeBytecode(buf)
if err != nil {
return nil, err
}
defer programPool.Put(program)
if e.cache != nil {
e.cache.Set(template, program)
}
}
result, err := e.Run(program, context)
if err != nil {
return nil, fmt.Errorf("execution error: %w", err)
}
return result, nil
}
func (e *Engine) Compile(template string) (*vm.Program, error) {
buf, err := e.compile(template)
if err != nil {
return nil, err
}
instructions, constants, err := bytecode.DeserializeBytecode(buf)
if err != nil {
return nil, fmt.Errorf("failed to deserialize bytecode: %w", err)
}
program := vm.NewProgram(instructions, constants)
if e.cache != nil {
e.cache.Set(template, program)
}
return program, nil
}
func (e *Engine) compile(template string) (*bytes.Buffer, error) {
lex := lexer.NewLexer(template)
defer lex.Release()
tokens := lex.Lex()
comp := compiler.NewCompiler(tokens)
defer comp.Release()
instructions, constants, err := comp.Compile(tokens)
if err != nil {
return nil, fmt.Errorf("compilation error: %w", err)
}
var buf bytes.Buffer
err = bytecode.SerializeBytecode(&buf, instructions, constants)
if err != nil {
return nil, fmt.Errorf("serialization error: %w", err)
}
return &buf, nil
}
func (e *Engine) deserializeBytecode(r io.Reader) (*vm.Program, error) {
instructions, constants, err := bytecode.DeserializeBytecode(r)
if err != nil {
return nil, fmt.Errorf("failed to deserialize bytecode: %w", err)
}
program := programPool.Get().(*vm.Program)
program.Instructions = instructions
program.Constants = constants
return program, nil
}
func (e *Engine) Run(program *vm.Program, context map[string]interface{}) ([]byte, error) {
vm := vm.NewVM(program.Instructions, context, program.Constants)
defer vm.Release()
result, err := vm.Run()
if err != nil {
return nil, fmt.Errorf("VM execution failed: %w", err)
}
return result, nil
}