-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmachine.go
365 lines (303 loc) · 12 KB
/
machine.go
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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// Package starlet provides powerful extensions and enriched wrappers for Starlark scripting.
//
// Its goal is to enhance the user's scripting experience by combining simplicity and functionality.
// It offers robust, thread-safe types such as Machine, which serves as a wrapper for Starlark runtime environments.
// With Starlet, users can easily manage global variables, load modules, and control the script execution flow.
package starlet
import (
"fmt"
"io/fs"
"sync"
itn "github.com/1set/starlet/internal"
"go.starlark.net/starlark"
)
// Machine is a thread-safe type that wraps Starlark runtime environments. Machine ensures thread safety by using a sync.RWMutex to control access to the environment's state.
//
// The Machine struct stores the state of the environment, including scripts, modules, and global variables. It provides methods for setting and getting these values, and for running the script. A Machine instance can be configured to preload modules and global variables before running a script or after resetting the environment. It can also lazyload modules right before running the script, the lazyload modules are defined in a list of module loaders and are invoked when the script is run.
//
// The global variables and preload modules can be set before the first run of the script or after resetting the environment. Additionally, extra variables can be set for each run of the script.
//
// Modules are divided into two types: preload and lazyload. Preload modules are loaded before the script is run, while lazyload modules are loaded as and when they are required during the script execution.
//
// The order of precedence for overriding is as follows: global variables, preload modules, and then extra variables before the run, while lazyload modules have the highest precedence during the run.
//
// Setting a print function allows the script to output text to the console or another output stream.
//
// The script to be run is defined by its name and content, and potentially a filesystem (fs.FS) if the script is to be loaded from a file.
//
// The result of each run is cached and written back to the environment, so that it can be used in the next run of the script.
//
// The environment can be reset, allowing the script to be run again with a fresh set of variables and modules.
type Machine struct {
_ itn.DoNotCompare
mu sync.RWMutex
// set variables
globals StringAnyMap
preloadMods ModuleLoaderList
lazyloadMods ModuleLoaderMap
printFunc PrintFunc
allowGlobalReassign bool
allowRecursion bool
enableInConv bool
enableOutConv bool
customTag string
// source code
scriptName string
scriptContent []byte
scriptFS fs.FS
// runtime core
progCache ByteCache
runTimes uint
loadCache *cache
thread *starlark.Thread
predeclared starlark.StringDict
}
func (m *Machine) String() string {
steps := uint64(0)
if m.thread != nil {
steps = m.thread.Steps
}
return fmt.Sprintf("🌠Machine{run:%d,step:%d,script:%q,len:%d,fs:%v}",
m.runTimes, steps, m.scriptName, len(m.scriptContent), m.scriptFS)
}
// PrintFunc is a function that tells Starlark how to print messages.
// If nil, the default `fmt.Fprintln(os.Stderr, msg)` will be used instead.
type PrintFunc func(thread *starlark.Thread, msg string)
// LoadFunc is a function that tells Starlark how to find and load other scripts
// using the load() function. If you don't use load() in your scripts, you can pass in nil.
type LoadFunc func(thread *starlark.Thread, module string) (starlark.StringDict, error)
// NewDefault creates a new Starlark runtime environment.
func NewDefault() *Machine {
return &Machine{enableInConv: true, enableOutConv: true}
}
// NewWithGlobals creates a new Starlark runtime environment with given global variables.
func NewWithGlobals(globals StringAnyMap) *Machine {
return &Machine{
enableInConv: true,
enableOutConv: true,
globals: globals,
}
}
// NewWithLoaders creates a new Starlark runtime environment with given global variables and preload & lazyload module loaders.
func NewWithLoaders(globals StringAnyMap, preload ModuleLoaderList, lazyload ModuleLoaderMap) *Machine {
return &Machine{
enableInConv: true,
enableOutConv: true,
globals: globals,
preloadMods: preload,
lazyloadMods: lazyload,
}
}
// NewWithBuiltins creates a new Starlark runtime environment with given global variables and all preload & lazyload built-in modules.
func NewWithBuiltins(globals StringAnyMap, additionalPreload ModuleLoaderList, additionalLazyload ModuleLoaderMap) *Machine {
pre := append(GetAllBuiltinModules(), additionalPreload...)
lazy := GetBuiltinModuleMap()
lazy.Merge(additionalLazyload)
return &Machine{
enableInConv: true,
enableOutConv: true,
globals: globals,
preloadMods: pre,
lazyloadMods: lazy,
}
}
// NewWithNames creates a new Starlark runtime environment with given global variables, preload and lazyload module names.
// The modules should be built-in modules, and it panics if any of the given modules fails to load.
func NewWithNames(globals StringAnyMap, preloads []string, lazyloads []string) *Machine {
pre, err := MakeBuiltinModuleLoaderList(preloads...)
if err != nil {
panic(err)
}
lazy, err := MakeBuiltinModuleLoaderMap(lazyloads...)
if err != nil {
panic(err)
}
return &Machine{
enableInConv: true,
enableOutConv: true,
globals: globals,
preloadMods: pre,
lazyloadMods: lazy,
}
}
// SetGlobals sets global variables in the Starlark runtime environment.
// These variables only take effect before the first run or after a reset.
func (m *Machine) SetGlobals(globals StringAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
m.globals = globals
}
// AddGlobals adds the globals of the Starlark runtime environment.
// These variables only take effect before the first run or after a reset.
func (m *Machine) AddGlobals(globals StringAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.globals == nil {
m.globals = make(StringAnyMap)
}
for k, v := range globals {
m.globals[k] = v
}
}
// GetGlobals gets the globals of the Starlark runtime environment.
func (m *Machine) GetGlobals() StringAnyMap {
m.mu.RLock()
defer m.mu.RUnlock()
return m.globals.Clone()
}
// SetPreloadModules sets the preload modules of the Starlark runtime environment.
// These modules only take effect before the first run or after a reset.
func (m *Machine) SetPreloadModules(mods ModuleLoaderList) {
m.mu.Lock()
defer m.mu.Unlock()
m.preloadMods = mods
}
// GetPreloadModules gets the preload modules of the Starlark runtime environment.
func (m *Machine) GetPreloadModules() ModuleLoaderList {
m.mu.RLock()
defer m.mu.RUnlock()
return m.preloadMods.Clone()
}
// AddPreloadModules adds the preload modules of the Starlark runtime environment.
// These modules only take effect before the first run or after a reset.
func (m *Machine) AddPreloadModules(mods ModuleLoaderList) {
m.mu.Lock()
defer m.mu.Unlock()
m.preloadMods = append(m.preloadMods, mods...)
}
// SetLazyloadModules sets the modules allowed to be loaded later of the Starlark runtime environment.
func (m *Machine) SetLazyloadModules(mods ModuleLoaderMap) {
m.mu.Lock()
defer m.mu.Unlock()
m.lazyloadMods = mods
}
// GetLazyloadModules gets the modules allowed to be loaded later of the Starlark runtime environment.
func (m *Machine) GetLazyloadModules() ModuleLoaderMap {
m.mu.RLock()
defer m.mu.RUnlock()
return m.lazyloadMods.Clone()
}
// AddLazyloadModules adds the modules allowed to be loaded later of the Starlark runtime environment.
func (m *Machine) AddLazyloadModules(mods ModuleLoaderMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.lazyloadMods == nil {
m.lazyloadMods = make(ModuleLoaderMap)
}
m.lazyloadMods.Merge(mods)
}
var (
// NoopPrintFunc is a no-op print function for the Starlark runtime environment, it does nothing.
NoopPrintFunc PrintFunc = func(thread *starlark.Thread, msg string) {}
)
// SetPrintFunc sets the print function of the Starlark runtime environment.
// To disable printing, you can set it to NoopPrintFunc. Setting it to nil will invoke the default `fmt.Fprintln(os.Stderr, msg)` instead.
func (m *Machine) SetPrintFunc(printFunc PrintFunc) {
m.mu.Lock()
defer m.mu.Unlock()
m.printFunc = printFunc
}
// SetScript sets the script related things of the Starlark runtime environment.
func (m *Machine) SetScript(name string, content []byte, fileSys fs.FS) {
m.mu.Lock()
defer m.mu.Unlock()
m.scriptName = name
m.scriptContent = content
m.scriptFS = fileSys
}
// SetScriptContent sets the script content of the Starlark runtime environment.
// It differs from SetScript in that it does not change the script name and filesystem.
func (m *Machine) SetScriptContent(content []byte) {
m.mu.Lock()
defer m.mu.Unlock()
m.scriptContent = content
}
// SetInputConversionEnabled controls the conversion of Starlark variables from input into Starlight wrappers.
func (m *Machine) SetInputConversionEnabled(enabled bool) {
m.mu.Lock() // Locking to avoid concurrent access
defer m.mu.Unlock()
m.enableInConv = enabled
}
// SetOutputConversionEnabled controls the conversion of Starlark variables from output into Starlight wrappers.
func (m *Machine) SetOutputConversionEnabled(enabled bool) {
m.mu.Lock() // Locking to avoid concurrent access
defer m.mu.Unlock()
m.enableOutConv = enabled
}
// SetScriptCache sets the cache for compiled Starlark programs.
func (m *Machine) SetScriptCache(cache ByteCache) {
m.mu.Lock() // Locking to avoid concurrent access
defer m.mu.Unlock()
m.progCache = cache
}
// SetScriptCacheEnabled controls the cache for compiled Starlark programs with the default in-memory cache.
// If enabled is true, it creates the default in-memory cache instance, otherwise it uses no cache.
func (m *Machine) SetScriptCacheEnabled(enabled bool) {
m.mu.Lock() // Locking to avoid concurrent access
defer m.mu.Unlock()
if enabled {
m.progCache = NewMemoryCache()
} else {
m.progCache = nil
}
}
// SetCustomTag sets the custom annotation tag of Go struct fields for Starlark.
func (m *Machine) SetCustomTag(tag string) {
m.mu.Lock() // Locking to avoid concurrent access
defer m.mu.Unlock()
m.customTag = tag
}
// GetStarlarkPredeclared returns the Starlark predeclared names of the Starlark runtime environment.
// It's for advanced usage only, don't use it unless you know what you are doing.
func (m *Machine) GetStarlarkPredeclared() starlark.StringDict {
m.mu.RLock()
defer m.mu.RUnlock()
return m.predeclared
}
// GetStarlarkThread returns the Starlark thread of the Starlark runtime environment.
// It's for advanced usage only, don't use it unless you know what you are doing.
func (m *Machine) GetStarlarkThread() *starlark.Thread {
m.mu.RLock()
defer m.mu.RUnlock()
return m.thread
}
// GetThreadLocal returns the local value of the Starlark thread of the Starlark runtime environment.
// It returns nil if the thread is not set or the key is not found. Please ensure the machine already runs before calling this method.
func (m *Machine) GetThreadLocal(key string) interface{} {
m.mu.RLock()
defer m.mu.RUnlock()
if m.thread == nil {
return nil
}
return m.thread.Local(key)
}
// Export returns the current variables of the Starlark runtime environment.
func (m *Machine) Export() StringAnyMap {
m.mu.RLock()
defer m.mu.RUnlock()
return m.convertOutput(m.predeclared)
}
// EnableRecursionSupport enables recursion support in all Starlark environments.
func (m *Machine) EnableRecursionSupport() {
m.mu.Lock()
defer m.mu.Unlock()
m.allowRecursion = true
}
// DisableRecursionSupport disables recursion support in all Starlark environments.
func (m *Machine) DisableRecursionSupport() {
m.mu.Lock()
defer m.mu.Unlock()
m.allowRecursion = false
}
// EnableGlobalReassign enables global reassignment in all Starlark environments.
func (m *Machine) EnableGlobalReassign() {
m.mu.Lock()
defer m.mu.Unlock()
m.allowGlobalReassign = true
}
// DisableGlobalReassign disables global reassignment in all Starlark environments.
func (m *Machine) DisableGlobalReassign() {
m.mu.Lock()
defer m.mu.Unlock()
m.allowGlobalReassign = false
}