-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy patheval.go
126 lines (111 loc) · 2.85 KB
/
eval.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
// Copyright (c) 2020-2023 Ozan Hacıbekiroğlu.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
package ugo
import (
"context"
)
// Eval compiles and runs scripts within same scope.
// If executed script's return statement has no value to return or return is
// omitted, it returns last value on stack.
// Warning: Eval is not safe to use concurrently.
type Eval struct {
Locals []Object
Globals Object
Opts CompilerOptions
VM *VM
ModulesCache []Object
}
// NewEval returns new Eval object.
func NewEval(opts CompilerOptions, globals Object, args ...Object) *Eval {
if globals == nil {
globals = Map{}
}
if opts.SymbolTable == nil {
opts.SymbolTable = NewSymbolTable()
}
if opts.moduleStore == nil {
opts.moduleStore = newModuleStore()
}
return &Eval{
Locals: args,
Globals: globals,
Opts: opts,
VM: NewVM(nil).SetRecover(true),
}
}
// Run compiles, runs given script and returns last value on stack.
func (r *Eval) Run(ctx context.Context, script []byte) (Object, *Bytecode, error) {
bytecode, err := Compile(script, r.Opts)
if err != nil {
return nil, nil, err
}
bytecode.Main.NumParams = bytecode.Main.NumLocals
r.Opts.Constants = bytecode.Constants
r.fixOpPop(bytecode)
r.VM.SetBytecode(bytecode)
if ctx == nil {
ctx = context.Background()
}
r.VM.modulesCache = r.ModulesCache
ret, err := r.run(ctx)
r.ModulesCache = r.VM.modulesCache
r.Locals = r.VM.GetLocals(r.Locals)
r.VM.Clear()
if err != nil {
return nil, bytecode, err
}
return ret, bytecode, nil
}
func (r *Eval) run(ctx context.Context) (ret Object, err error) {
ret = Undefined
doneCh := make(chan struct{})
// Always check whether context is done before running VM because
// parser and compiler may take longer than expected or context may be
// canceled for any reason before run, so use two selects.
select {
case <-ctx.Done():
r.VM.Abort()
err = ctx.Err()
default:
go func() {
defer close(doneCh)
ret, err = r.VM.Run(r.Globals, r.Locals...)
}()
select {
case <-ctx.Done():
r.VM.Abort()
<-doneCh
if err == nil {
err = ctx.Err()
}
case <-doneCh:
}
}
return
}
// fixOpPop changes OpPop and OpReturn Opcodes to force VM to return last value on top of stack.
func (*Eval) fixOpPop(bytecode *Bytecode) {
var prevOp byte
var lastOp byte
var fixPos int
IterateInstructions(bytecode.Main.Instructions,
func(pos int, opcode Opcode, operands []int, offset int) bool {
if prevOp == 0 {
prevOp = opcode
} else {
prevOp = lastOp
}
fixPos = -1
lastOp = opcode
if prevOp == OpPop && lastOp == OpReturn && operands[0] == 0 {
fixPos = pos - 1
}
return true
},
)
if fixPos > 0 {
bytecode.Main.Instructions[fixPos] = OpNoOp // overwrite OpPop
bytecode.Main.Instructions[fixPos+2] = 1 // set number of return to 1
}
}