-
Notifications
You must be signed in to change notification settings - Fork 0
/
api.go
198 lines (178 loc) · 5.29 KB
/
api.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
// Package bcl provides interpreting of the Basic Configuration Language (BCL)
// and storing the evaluated result in dynamic Blocks or static structs.
//
// - [Interpret] or [InterpretFile] parses and executes definitions
// from a BCL file, then creates Blocks
// - [CopyBlocks] takes Blocks and saves the content in static Go structs
// - [Unmarshal] = [Interpret] + [CopyBlocks]
// - [UnmarshalFile] = [InterpretFile] + [CopyBlocks]
//
// It is also possible to first [Parse], creating [Prog], and then [Execute] it.
// - [Interpret] = [Parse] + [Execute]
// - [InterpretFile] = [ParseFile] + [Execute]
//
// [Prog] can be dumped to a Writer with Dump and loaded with Load,
// there is also wrapper function [LoadProg], to load previously dumped Prog
// instead of using Parse on the BCL input.
package bcl
import "io"
// FileInput abstracts the input that is read from a file.
// It is going to be closed as soon as it's read.
// The only information needed from a file besides reading/closing
// is that it has a name.
type FileInput interface {
io.ReadCloser
Name() string
}
// Block is a dynamic result of running BCL [Interpret].
// It can be put into a static structure via [CopyBlocks].
type Block struct {
Type, Name string
Fields map[string]any
}
// Parse parses the input data, producing executable Prog.
func Parse(input []byte, name string, opts ...Option) (*Prog, error) {
c := make(chan string, 1)
c <- string(input)
close(c)
return parseWithOpts(c, name, opts)
}
// ParseFile reads and parses the input from a BCL file,
// producing executable Prog.
// The input file will be closed as soon as possible.
func ParseFile(f FileInput, opts ...Option) (prog *Prog, _ error) {
inpc := make(chan string)
rerr := make(chan error)
perr := make(chan error)
done := make(chan struct{})
go func() {
defer f.Close()
var b [4096]byte
for {
n, err := f.Read(b[:])
if err != nil && err != io.EOF {
rerr <- err
break
}
if err == io.EOF && n == 0 {
rerr <- nil
break
}
select {
case inpc <- string(b[:n]):
continue
case <-done:
rerr <- nil
return
}
}
close(inpc)
}()
go func() {
p, err := parseWithOpts(inpc, f.Name(), opts)
if err != nil {
close(done)
}
prog = p
perr <- err
}()
err, err2 := <-rerr, <-perr
if err == nil {
err = err2
}
return prog, err
}
func parseWithOpts(inputs <-chan string, name string, opts []Option) (*Prog, error) {
cf := makeConfig(opts)
prog, pstats, err := parse(inputs, name, parseConfig{cf.output, cf.logw})
if err == nil && cf.disasm {
prog.disasm()
}
if cf.stats {
printPStats(cf.output, pstats)
}
return prog, err
}
func LoadProg(r io.Reader, name string, opts ...Option) (*Prog, error) {
cf := makeConfig(opts)
prog := newProg(name, cf.output)
err := prog.Load(r)
if err == nil && cf.disasm {
prog.disasm()
}
return prog, err
}
// Execute executes the Prog, creating Blocks.
func Execute(prog *Prog, opts ...Option) (result []Block, err error) {
cf := makeConfig(opts)
result, xstats, err := execute(prog, vmConfig{cf.trace})
if cf.stats {
printXStats(cf.output, xstats)
}
return result, err
}
// Interpret parses and executes the BCL input, creating Blocks.
func Interpret(input []byte, opts ...Option) ([]Block, error) {
p, err := Parse(input, "input", opts...)
if err != nil {
return nil, err
}
return Execute(p, opts...)
}
// InterpretFile reads, parses and executes the input from a BCL file.
// The file will be closed as soon as possible.
func InterpretFile(f FileInput, opts ...Option) ([]Block, error) {
p, err := ParseFile(f, opts...)
if err != nil {
return nil, err
}
return Execute(p, opts...)
}
// CopyBlocks copies the blocks to the dest,
// which needs to be a pointer to a slice of structs.
//
// The requirements for the struct are:
// - struct type name should correspond to the BCL block type
// - struct needs the Name string field
// - for each block field, struct needs a corresponding field, of type as
// the evaluated value (currently supporting int, string and bool)
//
// The mentioned name correspondence is similar to handling json:
// as BCL is expected to use snake case, and Go struct - capitalized camel case,
// the snake underscores are simply removed and then the strings are compared,
// case-insensitive.
//
// The name corresponcence can be also set explicitly,
// by typing a tag keyed `bcl` at the struct field:
//
// type Record struct {
// Status string `bcl:"my_status"`
// }
//
// The lack of corresponding fields in the Go struct is reported as error.
// So is type mismatch of the fields.
//
// If the slice pointed by dest contained any elements, they are overwritten.
func CopyBlocks(dest any, blocks []Block) error {
return copyBlocks(dest, blocks)
}
// Unmarshal interprets the BCL input, and stores the result in dest,
// which should be a slice of structs.
// See [CopyBlocks] for a struct format.
func Unmarshal(input []byte, dest any) error {
res, err := Interpret(input)
if err != nil {
return err
}
return CopyBlocks(dest, res)
}
// UnmarshalFile interprets the BCL file and stores the result in dest,
// which should be a slice of structs.
// See [CopyBlocks] for a struct format.
func UnmarshalFile(f FileInput, dest any) error {
res, err := InterpretFile(f)
if err != nil {
return err
}
return CopyBlocks(dest, res)
}