Repository navigation
Expand file tree
/
Copy pathnumscript.go
More file actions
272 lines (224 loc) · 9.38 KB
/
Copy pathnumscript.go
File metadata and controls
272 lines (224 loc) · 9.38 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
package numscript
import (
"context"
"github.com/formancehq/numscript/internal/compiler"
"github.com/formancehq/numscript/internal/interpreter"
"github.com/formancehq/numscript/internal/parser"
"github.com/formancehq/numscript/internal/vm"
)
// This struct represents a parsed numscript source code
type ParseResult struct {
parseResult parser.ParseResult
}
// Returns a map from a variable's name to its type.
//
// doesn't include variables whose value is already defined within the script
func (p ParseResult) GetNeededVariables() map[string]string {
m := make(map[string]string)
if p.parseResult.Value.Vars == nil {
return m
}
for _, varDecl := range p.parseResult.Value.Vars.Declarations {
if varDecl.Name == nil || varDecl.Origin != nil {
continue
}
m[varDecl.Name.Name] = varDecl.Type.Name
}
return m
}
// func (*ParseResult) GetDiagnostics() []Diagnostic {}
type ParserError = parser.ParserError
func Parse(code string) ParseResult {
return ParseResult{parseResult: parser.Parse(code)}
}
var ParseErrorsToString = parser.ParseErrorsToString
func (p ParseResult) GetParsingErrors() []ParserError {
return p.parseResult.Errors
}
type (
VariablesMap = interpreter.VariablesMap
Posting = interpreter.Posting
ExecutionResult = interpreter.ExecutionResult
// For each account, list of the needed assets
BalanceQuery = interpreter.BalanceQuery
BalanceQueryItem = interpreter.BalanceQueryItem
MetadataQuery = interpreter.MetadataQuery
AccountBalance = interpreter.AccountBalance
Balances = interpreter.Balances
BalanceRow = interpreter.BalanceRow
// Input account metadata (opaque, string-valued) read via meta()
AccountMetadataRow = interpreter.AccountMetadataRow
AccountsMetadata = interpreter.AccountsMetadata
// The account metadata set during the execution (tagged, typed values)
SetAccountMetadataRow = interpreter.SetAccountMetadataRow
SetAccountsMetadata = interpreter.SetAccountsMetadata
// The transaction metadata, set by set_tx_meta()
Metadata = interpreter.Metadata
Store = interpreter.Store
StaticStore = interpreter.StaticStore
Value = interpreter.Value
InterpreterError = interpreter.InterpreterError
MissingFundsErr = interpreter.MissingFundsErr
NegativeAmountErr = interpreter.NegativeAmountErr
ResolvedDependencies = interpreter.ResolvedDependencies
AccountDependency = interpreter.AccountDependency
MetaDependency = interpreter.MetaDependency
)
var ErrScalingNotSupported = interpreter.ErrScalingNotSupported
func (p ParseResult) Run(ctx context.Context, vars VariablesMap, store Store) (ExecutionResult, InterpreterError) {
return p.RunWithFeatureFlags(ctx, vars, store, nil)
}
func (p ParseResult) RunWithFeatureFlags(
ctx context.Context,
vars VariablesMap,
store Store,
featureFlags map[string]struct{},
) (ExecutionResult, InterpreterError) {
if len(p.parseResult.Errors) != 0 {
return ExecutionResult{}, p.parseResult.Errors[0]
}
if featureFlags == nil {
featureFlags = make(map[string]struct{})
}
res, err := interpreter.RunProgram(ctx, p.parseResult.Value, vars, store, featureFlags)
if err != nil {
return ExecutionResult{}, err
}
return *res, nil
}
// ResolveDependencies statically determines which accounts and metadata the
// script reads and writes, resolving account/asset/key expressions against the
// given vars and store.
func (p ParseResult) ResolveDependencies(ctx context.Context, vars VariablesMap, store Store) (ResolvedDependencies, error) {
if len(p.parseResult.Errors) != 0 {
return ResolvedDependencies{}, p.parseResult.Errors[0]
}
return interpreter.ResolveDependencies(ctx, store, vars, p.parseResult.Value)
}
func (p ParseResult) GetSource() string {
return p.parseResult.Source
}
type (
VarsEncoder = compiler.VarsEncoder
CompiledProgram = vm.Program
VMStore = vm.Store
Vm = vm.Vm
Vars = vm.Vars
)
var NewVm = vm.NewVm
var DecodeVars = vm.DecodeVars
func (p ParseResult) Compile() (VarsEncoder, CompiledProgram, error) {
return p.CompileWithFeatureFlags(nil)
}
// CompileWithFeatureFlags compiles the program, rejecting any construct gated
// behind an experimental feature flag that isn't in featureFlags.
func (p ParseResult) CompileWithFeatureFlags(featureFlags map[string]struct{}) (VarsEncoder, CompiledProgram, error) {
if len(p.parseResult.Errors) != 0 {
return VarsEncoder{}, CompiledProgram{}, p.parseResult.Errors[0]
}
if featureFlags == nil {
featureFlags = make(map[string]struct{})
}
return compiler.Compile(p.parseResult.Value, featureFlags)
}
func Compile(source string) (VarsEncoder, CompiledProgram, error) {
return Parse(source).Compile()
}
func CompileWithFeatureFlags(source string, featureFlags map[string]struct{}) (VarsEncoder, CompiledProgram, error) {
return Parse(source).CompileWithFeatureFlags(featureFlags)
}
var DecodeCompiledProgram = vm.DecodeProgram
// BytecodeVersion is the version of the bytecode wire format a compiled
// program or an encoded Vars was written with — major.minor, versioned
// independently of the library itself: a new library release does not imply a
// new bytecode version. A reader accepts a blob of its own major with a minor
// no newer than its own (BytecodeVersion.CanRead); anything else the decoders
// reject with UnsupportedBytecodeVersionError. Major 0 is unstable: a 0.x build
// reads only its exact version, so a host holding a 0.x blob this build
// rejects should recompile the script from source.
//
// CurrentBytecodeVersion is what this build's Compile and Encode write and the
// newest it can execute. A host that stores bytecode compiled by one build and
// executes it with another can compare it against the stored blob's version
// before trusting the bytecode to run; PeekCompiledProgramVersion and
// PeekVarsVersion read that version from the raw bytes without decoding the
// rest, and without checking that this build can read it.
type (
BytecodeVersion = vm.BytecodeVersion
UnsupportedBytecodeVersionError = vm.UnsupportedBytecodeVersionError
)
var (
CurrentBytecodeVersion = vm.CurrentBytecodeVersion
PeekCompiledProgramVersion = vm.PeekProgramVersion
PeekVarsVersion = vm.PeekVarsVersion
)
// VerifyCompiledProgram statically checks that a program is safe to execute:
// ExecVm assumes well-formed bytecode and will panic rather than error on a
// program that is not. Compile's output always is, so this is for programs that
// came from somewhere else — DecodeCompiledProgram, most obviously.
//
// VerifyCompiledProgramWithVars additionally checks the program against the vars
// it will be given; prefer it whenever vars are in play, since a program that
// loads a variable is only safe against a pool that actually has it. On
// success it also returns a VerifiedVarsInfo: a caller that reuses the same
// compiled Program across many calls (e.g. an LRU cache keyed on the compiled
// bytes) can keep this and use its CheckVars method to skip re-running
// verification — a whole-program static pass — when it sees a Vars shape it
// already knows is compatible.
type VerifiedVarsInfo = vm.VerifiedVarsInfo
var (
VerifyCompiledProgram = vm.Verify
VerifyCompiledProgramWithVars = vm.VerifyWithVars
)
// VM execution error types, aliased so ExecVm callers can classify failures
// with errors.As without reaching into internal packages — the same pattern as
// the interpreter's error types above. The Vm prefix keeps them apart from the
// interpreter's MissingFundsErr/NegativeAmountErr, which are different types
// with different fields.
type (
VmMissingFundsError = vm.MissingFundsError
VmNegativeAmountError = vm.NegativeAmountError
VmNegativeBalanceError = vm.NegativeBalanceError
VmAssetMismatchError = vm.AssetMismatchError
VmInvalidAllotmentSum = vm.InvalidAllotmentSum
VmNegativePortionError = vm.NegativePortionError
VmDivideByZeroError = vm.DivideByZeroError
VmInvalidAccountName = vm.InvalidAccountName
VmInvalidColor = vm.InvalidColor
VmInvalidScope = vm.InvalidScope
VmCannotCastScopedAccountToString = vm.CannotCastScopedAccountToString
VmInvalidUncappedSource = vm.InvalidUncappedSource
VmMetadataNotFoundError = vm.MetadataNotFoundError
VmBadMetaValueError = vm.BadMetaValueError
VmInternalError = vm.InternalError
VmInvalidPostingError = vm.InvalidPostingError
VmStoreError = vm.StoreError
)
func ExecVm[S VMStore](ctx context.Context, machine *Vm, vars *Vars, store S) (ExecutionResult, error) {
res, execErr := vm.Exec(ctx, machine, vars, store)
if execErr != nil {
return ExecutionResult{}, execErr
}
// Postings share one type (funds.Posting), scope fields included, so they
// pass through unchanged. Metadata is normalized to the interpreter's
// contract: non-nil maps/slices, account rows in the SetAccountsMetadata
// shape.
txMeta := res.Metadata
if txMeta == nil {
txMeta = Metadata{}
}
accountsMeta := make(SetAccountsMetadata, 0, len(res.AccountsMetadata))
for _, e := range res.AccountsMetadata {
accountsMeta = append(accountsMeta, SetAccountMetadataRow{
Account: e.Account,
Key: e.Key,
Value: e.Value,
Scope: e.Scope,
})
}
return ExecutionResult{
Postings: res.Postings,
Metadata: txMeta,
AccountsMetadata: accountsMeta,
}, nil
}