-
Notifications
You must be signed in to change notification settings - Fork 0
/
script.go
230 lines (202 loc) · 4.07 KB
/
script.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
// SPDX-FileCopyrightText: 2019 M. Shulhan <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later
package awwan
import (
"bytes"
"fmt"
"os"
)
// Script define the content of ".aww" file, line by line.
type Script struct {
stmts []*Statement
requires []*Statement
rawLines [][]byte
}
// NewScript load the content of awwan script (".aww"), apply the
// value of session variables into the script content, and split it into
// Statements.
func NewScript(ses *Session, path string) (script *Script, err error) {
var (
logp = `NewScript`
content []byte
)
content, err = os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("%s: %w", logp, err)
}
script, err = ParseScript(ses, path, content)
if err != nil {
return nil, fmt.Errorf("%s: %w", logp, err)
}
return script, nil
}
// ParseScript parse the script content by applying the session
// variables and splitting it into Statement.
func ParseScript(ses *Session, path string, content []byte) (script *Script, err error) {
var (
logp = `ParseScript`
stmt *Statement
line []byte
raw []byte
splits [][]byte
rawLines [][]byte
stmts []*Statement
requires []*Statement
x int
)
raw, err = ses.render(path, content)
if err != nil {
return nil, fmt.Errorf(`%s: %w`, logp, err)
}
raw = bytes.TrimRight(raw, " \t\r\n\v")
splits = bytes.Split(raw, []byte{'\n'})
// Add empty line at the beginning to make the start index start from
// 1, not 0.
rawLines = [][]byte{{'\n'}}
rawLines = append(rawLines, splits...)
rawLines = joinStatements(rawLines)
rawLines = joinRequireStatements(rawLines)
stmts = make([]*Statement, len(rawLines))
requires = make([]*Statement, len(rawLines))
for x, line = range rawLines {
stmt, err = ParseStatement(line)
if err != nil {
return nil, fmt.Errorf("%s: line %d: %w", logp, x, err)
}
if stmt == nil {
continue
}
if stmt.kind == statementKindRequire {
requires[x] = stmt
} else {
stmts[x] = stmt
}
}
script = &Script{
stmts: stmts,
requires: requires,
rawLines: rawLines,
}
return script, nil
}
// joinRequireStatements join the "#require:" statement into one line.
// For example,
//
// #require:
// a
// b
//
// will be transformed into
//
// #require: a
// b
//
// and
//
// #require: a
// b
//
// will be leave as is.
func joinRequireStatements(in [][]byte) (out [][]byte) {
var (
stmt []byte
x int
)
out = make([][]byte, len(in))
if len(in) > 0 {
out[0] = in[0]
}
for x = 1; x < len(in); x++ {
stmt = in[x]
if !bytes.HasPrefix(stmt, cmdMagicRequire) {
out[x] = in[x]
continue
}
stmt = stmt[len(cmdMagicRequire):]
if len(stmt) != 0 {
// #require: already has command on the same line.
out[x] = in[x]
continue
}
if x+1 == len(in) {
break
}
if in[x+1][0] == '#' {
// Empty require statement followed by comment or
// magic command.
out[x] = nil
continue
}
stmt = in[x]
stmt = append(stmt, ' ')
stmt = append(stmt, in[x+1]...)
out[x] = stmt
in[x+1] = nil
}
return out
}
// joinStatements join multiline statements that ends with "\" into single
// line.
//
// For example,
//
// a\
// b
// c
//
// will become,
//
// a b
// c
func joinStatements(in [][]byte) (out [][]byte) {
var (
stmt []byte
nextStmt []byte
x int
y int
endc int
lastc byte
)
out = make([][]byte, len(in))
if len(in) > 0 {
out[0] = nil
}
for x = 1; x < len(in); x++ {
stmt = bytes.TrimSpace(in[x])
if len(stmt) == 0 {
in[x] = nil
out[x] = nil
continue
}
endc = len(stmt) - 1
if stmt[endc] != '\\' {
in[x] = nil
out[x] = stmt
continue
}
// Start joining multiline statement.
stmt = bytes.TrimRight(stmt, "\\")
y = x + 1
for ; y < len(in); y++ {
nextStmt = in[y]
if len(nextStmt) == 0 {
in[y] = nil
out[y] = nil
break
}
endc = len(nextStmt) - 1
lastc = nextStmt[endc]
if lastc == '\\' {
nextStmt = bytes.TrimRight(nextStmt, "\\")
}
stmt = append(stmt, nextStmt...)
in[y] = nil
if lastc != '\\' {
break
}
}
out[x] = stmt
x = y
}
return out
}