/
global_state.go
355 lines (288 loc) · 9.76 KB
/
global_state.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
package core
import (
"errors"
"fmt"
"io"
"reflect"
"sync"
"sync/atomic"
"github.com/inoxlang/inox/internal/core/symbolic"
"github.com/inoxlang/inox/internal/utils"
"github.com/rs/zerolog"
"golang.org/x/exp/maps"
)
const (
MINIMAL_STATE_ID = 1
INITIAL_MODULE_HEAP_CAPACITY = 1000
MINIMUM_MOD_PRIORITY = ModulePriority(10)
READ_TX_PRIORITY = ModulePriority(50)
READ_WRITE_TX_PRIORITY = ModulePriority(150)
)
var (
ErrContextInUse = errors.New("cannot create a new global state with a context that already has an associated state")
ErrOutAndLoggerAlreadySet = errors.New(".Out & .Logger are already definitely set")
GLOBAL_STATE_PROPNAMES = []string{"module"}
previousStateId atomic.Int64
)
// A GlobalState represents the global state of a module (or the shell loop), most exported fields should be set once.
// Patterns, host aliases and host definition data are stored in the context.
type GlobalState struct {
id StateId
Module *Module //nil in some cases (e.g. shell, mapping entry's state), TODO: check for usage
//output
Out io.Writer //io.Discard by default
Logger zerolog.Logger //zerolog.Nop() by default
LogLevels *LogLevels //DEFAULT_LOG_LEVELS by default
OutputFieldsInitialized atomic.Bool //should be set to true by the state's creator, even if the default values are kept.
// most relevant components
Ctx *Context
Manifest *Manifest
Project Project //can be nil
Bytecode *Bytecode //can be nil
Globals GlobalVariables //global variables
LThread *LThread //not nil if running in a dedicated LThread
Databases map[string]*DatabaseIL //the map should never change
Heap *ModuleHeap
SystemGraph *SystemGraph
lockedValues []PotentiallySharable
//
goCallArgPrepBuf []any
goCallArgsBuf []reflect.Value
// related states
MainState *GlobalState //never nil except for parents of main states,this field should be set by user of GlobalState.
descendantStates map[ResourceName]*GlobalState
descendantStatesLock sync.Mutex
// factories for building the state of any imported module
GetBaseGlobalsForImportedModule func(ctx *Context, manifest *Manifest) (GlobalVariables, error) // ok if nil
GetBasePatternsForImportedModule func() (map[string]Pattern, map[string]*PatternNamespace) // return nil maps by default
SymbolicBaseGlobalsForImportedModule map[string]symbolic.Value // ok if nil, should not be modified
// debugging and testing
Debugger atomic.Value //nil or (nillable) *Debugger
TestingState TestingState
//errors & check data
PrenitStaticCheckErrors []*StaticCheckError
MainPreinitError error
FirstDatabaseOpeningError error
StaticCheckData *StaticCheckData
SymbolicData *SymbolicData
}
type StateId int64
type ModulePriority uint32
// NewGlobalState creates a state with the provided context and constants.
// The OutputFieldsInitialized field is not initialized and should be set by the caller.
func NewGlobalState(ctx *Context, constants ...map[string]Value) *GlobalState {
if ctx.state != nil {
panic(ErrContextInUse)
}
state := &GlobalState{
id: StateId(previousStateId.Add(1)),
Ctx: ctx,
SymbolicData: &SymbolicData{Data: symbolic.NewSymbolicData()},
descendantStates: make(map[ResourceName]*GlobalState, 0),
Out: io.Discard,
Logger: zerolog.Nop(),
LogLevels: DEFAULT_LOG_LEVELS,
GetBasePatternsForImportedModule: func() (map[string]Pattern, map[string]*PatternNamespace) {
return nil, nil
},
goCallArgPrepBuf: make([]any, 10),
goCallArgsBuf: make([]reflect.Value, 10),
//TODO: use a heap type suited to the module's type and its expected lifespan.
Heap: NewArenaHeap(INITIAL_MODULE_HEAP_CAPACITY),
}
ctx.SetClosestState(state)
globals := map[string]Value{}
for _, arg := range constants {
for k, v := range arg {
if v.IsMutable() {
panic(fmt.Errorf("error while creating a new state: constant global %s is mutable", k))
}
globals[k] = v
}
}
state.Globals = GlobalVariablesFromMap(globals, maps.Keys(globals))
return state
}
// IsMain returns true if g.MainState == g.
func (g *GlobalState) IsMain() bool {
if g.MainState == nil {
panic(ErrUnreachable)
}
return g.MainState == g
}
func (g *GlobalState) SetDescendantState(src ResourceName, state *GlobalState) {
g.descendantStatesLock.Lock()
defer g.descendantStatesLock.Unlock()
if _, ok := g.descendantStates[src]; ok {
panic(fmt.Errorf("descendant state of %s already set", src.ResourceName()))
}
g.descendantStates[src] = state
if g.MainState != nil && g.MainState != g {
g.MainState.SetDescendantState(src, state)
}
}
func (g *GlobalState) ComputePriority() ModulePriority {
tx := g.Ctx.GetTx()
if tx == nil {
return MINIMUM_MOD_PRIORITY
}
if tx.IsReadonly() {
return READ_TX_PRIORITY
}
return READ_WRITE_TX_PRIORITY
}
func (g *GlobalState) InitSystemGraph() {
if g.SystemGraph != nil {
return
}
g.SystemGraph = NewSystemGraph()
}
func (g *GlobalState) ProposeSystemGraph(v SystemGraphNodeValue, optionalName string) {
if g.SystemGraph != nil {
v.ProposeSystemGraph(g.Ctx, g.SystemGraph, optionalName, nil)
}
}
func (g *GlobalState) GetGoMethod(name string) (*GoFunction, bool) {
return nil, false
}
func (g *GlobalState) Prop(ctx *Context, name string) Value {
switch name {
case "module":
return g.Module
}
method, ok := g.GetGoMethod(name)
if !ok {
panic(FormatErrPropertyDoesNotExist(name, g))
}
return method
}
func (*GlobalState) SetProp(ctx *Context, name string, value Value) error {
return ErrCannotSetProp
}
func (*GlobalState) PropertyNames(ctx *Context) []string {
return ROUTINE_GROUP_PROPNAMES
}
// A GlobalVariables represents the global scope of a module.
// Global variables captured by shared Inox functions are temporarily added during calls.
type GlobalVariables struct {
//start constants are all the constants set before the code starts its execution,
//therefore that include base constants & constants defined before the manifest.
startConstants []string
permanent map[string]Value
capturedGlobalsStack [][]capturedGlobal
}
func GlobalVariablesFromMap(m map[string]Value, startConstants []string) GlobalVariables {
if m == nil {
m = make(map[string]Value)
}
return GlobalVariables{permanent: m, startConstants: startConstants}
}
func (g *GlobalVariables) Get(name string) Value {
if len(g.capturedGlobalsStack) != 0 {
for _, captured := range g.capturedGlobalsStack[len(g.capturedGlobalsStack)-1] {
if captured.name == name {
return captured.value
}
}
}
return g.permanent[name]
}
func (g *GlobalVariables) CheckedGet(name string) (Value, bool) {
if len(g.capturedGlobalsStack) != 0 {
for _, captured := range g.capturedGlobalsStack[len(g.capturedGlobalsStack)-1] {
if captured.name == name {
return captured.value, true
}
}
}
v, ok := g.permanent[name]
return v, ok
}
func (g *GlobalVariables) Has(name string) bool {
if len(g.capturedGlobalsStack) != 0 {
for _, captured := range g.capturedGlobalsStack[len(g.capturedGlobalsStack)-1] {
if captured.name == name {
return true
}
}
}
_, ok := g.permanent[name]
return ok
}
func (g *GlobalVariables) Foreach(fn func(name string, v Value, isStartConstant bool) error) error {
if len(g.capturedGlobalsStack) != 0 {
for _, captured := range g.capturedGlobalsStack[len(g.capturedGlobalsStack)-1] {
err := fn(captured.name, captured.value, false) //TODO: never a constant global ?
if err != nil {
return err
}
}
}
for k, v := range g.permanent {
isStartConstant := utils.SliceContains(g.startConstants, k)
err := fn(k, v, isStartConstant)
if err != nil {
return err
}
}
return nil
}
// Set sets the value for a global variable (not constant)
func (g *GlobalVariables) Set(name string, value Value) {
if utils.SliceContains(g.startConstants, name) {
panic(fmt.Errorf("cannot change value of global constant %s", name))
}
if len(g.capturedGlobalsStack) != 0 {
for _, captured := range g.capturedGlobalsStack[len(g.capturedGlobalsStack)-1] {
if captured.name == name {
panic(ErrAttemptToSetCaptureGlobal)
}
}
}
g.permanent[name] = value
}
// SetChecked sets the value for a global variable (not constant), it called
func (g *GlobalVariables) SetCheck(name string, value Value, allow func(defined bool) error) error {
if utils.SliceContains(g.startConstants, name) {
panic(fmt.Errorf("cannot change value of global constant %s", name))
}
if len(g.capturedGlobalsStack) != 0 {
for _, captured := range g.capturedGlobalsStack[len(g.capturedGlobalsStack)-1] {
if captured.name == name {
panic(ErrAttemptToSetCaptureGlobal)
}
}
}
_, alreadyDefined := g.permanent[name]
if err := allow(alreadyDefined); err != nil {
return err
}
g.permanent[name] = value
return nil
}
func (g *GlobalVariables) PushCapturedGlobals(captured []capturedGlobal) {
g.capturedGlobalsStack = append(g.capturedGlobalsStack, captured)
}
func (g *GlobalVariables) PopCapturedGlobals() {
g.capturedGlobalsStack = g.capturedGlobalsStack[:len(g.capturedGlobalsStack)-1]
}
func (g *GlobalVariables) Entries() map[string]Value {
_map := maps.Clone(g.permanent)
if len(g.capturedGlobalsStack) != 0 {
for _, captured := range g.capturedGlobalsStack[len(g.capturedGlobalsStack)-1] {
_map[captured.name] = captured.value
}
}
return _map
}
func (g *GlobalVariables) Constants() map[string]Value {
constants := make(map[string]Value, len(g.startConstants))
for _, name := range g.startConstants {
val := g.permanent[name]
if val == nil {
panic(fmt.Errorf("value of constant %s is nil", val))
}
constants[name] = val
}
return constants
}