bare-script 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +132 -0
- package/bin/bareScriptDoc.js +15 -0
- package/lib/library.js +978 -0
- package/lib/libraryDoc.js +136 -0
- package/lib/model.js +477 -0
- package/lib/parser.js +725 -0
- package/lib/runtime.js +316 -0
- package/lib/runtimeAsync.js +379 -0
- package/package.json +36 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Licensed under the MIT License
|
|
2
|
+
// https://github.com/craigahobbs/bare-script/blob/main/LICENSE
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse the library source for documentation tags
|
|
7
|
+
*
|
|
8
|
+
* @param {string[][]} files - The list of file name and source tuples
|
|
9
|
+
* @returns {Object} The [library documentation model]{@link https://craigahobbs.github.io/bare-script/library/#var.vDoc=1}
|
|
10
|
+
* @throws {Error}
|
|
11
|
+
* @ignore
|
|
12
|
+
*/
|
|
13
|
+
export function parseLibraryDoc(files) {
|
|
14
|
+
// Parse each source file line-by-line
|
|
15
|
+
const errors = [];
|
|
16
|
+
const funcs = {};
|
|
17
|
+
let func = null;
|
|
18
|
+
for (const [file, source] of files) {
|
|
19
|
+
const lines = source.split(rSplit);
|
|
20
|
+
for (const [ixLine, line] of lines.entries()) {
|
|
21
|
+
// function/group/doc/return documentation keywords?
|
|
22
|
+
const matchKey = line.match(rKey);
|
|
23
|
+
if (matchKey !== null) {
|
|
24
|
+
const {key, text} = matchKey.groups;
|
|
25
|
+
const textTrim = text.trim();
|
|
26
|
+
|
|
27
|
+
// Keyword used outside of function?
|
|
28
|
+
if (key !== 'function' && func === null) {
|
|
29
|
+
errors.push(`${file}:${ixLine + 1}: ${key} keyword outside function`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Process the keyword
|
|
34
|
+
if (key === 'group') {
|
|
35
|
+
if (textTrim === '') {
|
|
36
|
+
errors.push(`${file}:${ixLine + 1}: Invalid function group name "${textTrim}"`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if ('group' in func) {
|
|
40
|
+
errors.push(`${file}:${ixLine + 1}: Function "${func.name}" group redefinition`);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Set the function group
|
|
45
|
+
func.group = textTrim;
|
|
46
|
+
} else if (key === 'doc' || key === 'return') {
|
|
47
|
+
// Add the documentation line - don't add leading blank lines
|
|
48
|
+
let funcDoc = func[key] ?? null;
|
|
49
|
+
if (funcDoc !== null || textTrim !== '') {
|
|
50
|
+
if (funcDoc === null) {
|
|
51
|
+
funcDoc = [];
|
|
52
|
+
func[key] = funcDoc;
|
|
53
|
+
}
|
|
54
|
+
funcDoc.push(text);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
// key === 'function'
|
|
58
|
+
if (textTrim === '') {
|
|
59
|
+
errors.push(`${file}:${ixLine + 1}: Invalid function name "${textTrim}"`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (textTrim in funcs) {
|
|
63
|
+
errors.push(`${file}:${ixLine + 1}: Function "${textTrim}" redefinition`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Add the function
|
|
68
|
+
func = {'name': textTrim};
|
|
69
|
+
funcs[textTrim] = func;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
// arg keyword?
|
|
73
|
+
const matchArg = line.match(rArg);
|
|
74
|
+
if (matchArg !== null) {
|
|
75
|
+
const {name, text} = matchArg.groups;
|
|
76
|
+
const textTrim = text.trim();
|
|
77
|
+
|
|
78
|
+
// Keyword used outside of function?
|
|
79
|
+
if (func === null) {
|
|
80
|
+
errors.push(`${file}:${ixLine + 1}: Function argument "${name}" outside function`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Add the function arg documentation line - don't add leading blank lines
|
|
85
|
+
let args = func.args ?? null;
|
|
86
|
+
let arg = (args !== null ? args.find((argFind) => argFind.name === name) : null) ?? null;
|
|
87
|
+
if (arg !== null || textTrim !== '') {
|
|
88
|
+
if (args === null) {
|
|
89
|
+
args = [];
|
|
90
|
+
func.args = args;
|
|
91
|
+
}
|
|
92
|
+
if (arg === null) {
|
|
93
|
+
arg = {'name': name, 'doc': []};
|
|
94
|
+
args.push(arg);
|
|
95
|
+
}
|
|
96
|
+
arg.doc.push(text);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create the library documentation model
|
|
104
|
+
const library = {
|
|
105
|
+
'functions': Object.values(funcs).sort(
|
|
106
|
+
/* c8 ignore next */
|
|
107
|
+
(funcA, funcB) => (funcA.name < funcB.name ? -1 : (funcA.name === funcB.name ? 0 : 1))
|
|
108
|
+
)
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Validate
|
|
112
|
+
if (library.functions.length === 0) {
|
|
113
|
+
errors.push('error: No library functions');
|
|
114
|
+
}
|
|
115
|
+
for (const funcLib of library.functions) {
|
|
116
|
+
if (!('group' in funcLib)) {
|
|
117
|
+
errors.push(`error: Function "${funcLib.name}" missing group`);
|
|
118
|
+
}
|
|
119
|
+
if (!('doc' in funcLib)) {
|
|
120
|
+
errors.push(`error: Function "${funcLib.name}" missing documentation`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Errors?
|
|
125
|
+
if (errors.length !== 0) {
|
|
126
|
+
throw new Error(errors.join('\n'));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return library;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
// Library documentation regular expressions
|
|
134
|
+
const rKey = /^\s*(?:\/\/|#)\s*\$(?<key>function|group|doc|return):\s?(?<text>.*)$/;
|
|
135
|
+
const rArg = /^\s*(?:\/\/|#)\s*\$arg\s+(?<name>[A-Za-z_][A-Za-z0-9_]*):\s?(?<text>.*)$/;
|
|
136
|
+
const rSplit = /\r?\n/;
|
package/lib/model.js
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// Licensed under the MIT License
|
|
2
|
+
// https://github.com/craigahobbs/bare-script/blob/main/LICENSE
|
|
3
|
+
|
|
4
|
+
/** @module lib/model */
|
|
5
|
+
|
|
6
|
+
import {parseSchemaMarkdown} from 'schema-markdown/lib/parser.js';
|
|
7
|
+
import {validateType} from 'schema-markdown/lib/schema.js';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The BareScript type model
|
|
12
|
+
*/
|
|
13
|
+
export const bareScriptTypes = parseSchemaMarkdown(`\
|
|
14
|
+
# A BareScript script
|
|
15
|
+
struct BareScript
|
|
16
|
+
|
|
17
|
+
# The script's statements
|
|
18
|
+
ScriptStatement[] statements
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# A script statement
|
|
22
|
+
union ScriptStatement
|
|
23
|
+
|
|
24
|
+
# An expression
|
|
25
|
+
ExpressionStatement expr
|
|
26
|
+
|
|
27
|
+
# A jump statement
|
|
28
|
+
JumpStatement jump
|
|
29
|
+
|
|
30
|
+
# A return statement
|
|
31
|
+
ReturnStatement return
|
|
32
|
+
|
|
33
|
+
# A label definition
|
|
34
|
+
string label
|
|
35
|
+
|
|
36
|
+
# A function definition
|
|
37
|
+
FunctionStatement function
|
|
38
|
+
|
|
39
|
+
# An include statement
|
|
40
|
+
IncludeStatement include
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# An expression statement
|
|
44
|
+
struct ExpressionStatement
|
|
45
|
+
|
|
46
|
+
# The variable name to assign the expression value
|
|
47
|
+
optional string name
|
|
48
|
+
|
|
49
|
+
# The expression to evaluate
|
|
50
|
+
Expression expr
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# A jump statement
|
|
54
|
+
struct JumpStatement
|
|
55
|
+
|
|
56
|
+
# The label to jump to
|
|
57
|
+
string label
|
|
58
|
+
|
|
59
|
+
# The test expression
|
|
60
|
+
optional Expression expr
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# A return statement
|
|
64
|
+
struct ReturnStatement
|
|
65
|
+
|
|
66
|
+
# The expression to return
|
|
67
|
+
optional Expression expr
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# A function definition statement
|
|
71
|
+
struct FunctionStatement
|
|
72
|
+
|
|
73
|
+
# If true, the function is defined as async
|
|
74
|
+
optional bool async
|
|
75
|
+
|
|
76
|
+
# The function name
|
|
77
|
+
string name
|
|
78
|
+
|
|
79
|
+
# The function's argument names
|
|
80
|
+
optional string[len > 0] args
|
|
81
|
+
|
|
82
|
+
# The function's statements
|
|
83
|
+
ScriptStatement[] statements
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# An include statement
|
|
87
|
+
struct IncludeStatement
|
|
88
|
+
|
|
89
|
+
# The list of include scripts to load and execute in the global scope
|
|
90
|
+
IncludeScript[len > 0] includes
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# An include script
|
|
94
|
+
struct IncludeScript
|
|
95
|
+
|
|
96
|
+
# The include script URL
|
|
97
|
+
string url
|
|
98
|
+
|
|
99
|
+
# If true, this is a system include
|
|
100
|
+
optional bool system
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# An expression
|
|
104
|
+
union Expression
|
|
105
|
+
|
|
106
|
+
# A number literal
|
|
107
|
+
float number
|
|
108
|
+
|
|
109
|
+
# A string literal
|
|
110
|
+
string string
|
|
111
|
+
|
|
112
|
+
# A variable value
|
|
113
|
+
string variable
|
|
114
|
+
|
|
115
|
+
# A function expression
|
|
116
|
+
FunctionExpression function
|
|
117
|
+
|
|
118
|
+
# A binary expression
|
|
119
|
+
BinaryExpression binary
|
|
120
|
+
|
|
121
|
+
# A unary expression
|
|
122
|
+
UnaryExpression unary
|
|
123
|
+
|
|
124
|
+
# An expression group
|
|
125
|
+
Expression group
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# A binary expression
|
|
129
|
+
struct BinaryExpression
|
|
130
|
+
|
|
131
|
+
# The binary expression operator
|
|
132
|
+
BinaryExpressionOperator op
|
|
133
|
+
|
|
134
|
+
# The left expression
|
|
135
|
+
Expression left
|
|
136
|
+
|
|
137
|
+
# The right expression
|
|
138
|
+
Expression right
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# A binary expression operator
|
|
142
|
+
enum BinaryExpressionOperator
|
|
143
|
+
|
|
144
|
+
# Exponentiation
|
|
145
|
+
"**"
|
|
146
|
+
|
|
147
|
+
# Multiplication
|
|
148
|
+
"*"
|
|
149
|
+
|
|
150
|
+
# Division
|
|
151
|
+
"/"
|
|
152
|
+
|
|
153
|
+
# Remainder
|
|
154
|
+
"%"
|
|
155
|
+
|
|
156
|
+
# Addition
|
|
157
|
+
"+"
|
|
158
|
+
|
|
159
|
+
# Subtraction
|
|
160
|
+
"-"
|
|
161
|
+
|
|
162
|
+
# Less than or equal
|
|
163
|
+
"<="
|
|
164
|
+
|
|
165
|
+
# Less than
|
|
166
|
+
"<"
|
|
167
|
+
|
|
168
|
+
# Greater than or equal
|
|
169
|
+
">="
|
|
170
|
+
|
|
171
|
+
# Greater than
|
|
172
|
+
">"
|
|
173
|
+
|
|
174
|
+
# Equal
|
|
175
|
+
"=="
|
|
176
|
+
|
|
177
|
+
# Not equal
|
|
178
|
+
"!="
|
|
179
|
+
|
|
180
|
+
# Logical AND
|
|
181
|
+
"&&"
|
|
182
|
+
|
|
183
|
+
# Logical OR
|
|
184
|
+
"||"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# A unary expression
|
|
188
|
+
struct UnaryExpression
|
|
189
|
+
|
|
190
|
+
# The unary expression operator
|
|
191
|
+
UnaryExpressionOperator op
|
|
192
|
+
|
|
193
|
+
# The expression
|
|
194
|
+
Expression expr
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# A unary expression operator
|
|
198
|
+
enum UnaryExpressionOperator
|
|
199
|
+
|
|
200
|
+
# Unary negation
|
|
201
|
+
"-"
|
|
202
|
+
|
|
203
|
+
# Logical NOT
|
|
204
|
+
"!"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# A function expression
|
|
208
|
+
struct FunctionExpression
|
|
209
|
+
|
|
210
|
+
# The function name
|
|
211
|
+
string name
|
|
212
|
+
|
|
213
|
+
# The function arguments
|
|
214
|
+
optional Expression[] args
|
|
215
|
+
`);
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Validate a BareScript script model
|
|
220
|
+
*
|
|
221
|
+
* @param {Object} script - The [BareScript model]{@link https://craigahobbs.github.io/bare-script/model/#var.vName='BareScript'}
|
|
222
|
+
* @returns {Object} The validated BareScript model
|
|
223
|
+
* @throws [ValidationError]{@link https://craigahobbs.github.io/schema-markdown-js/module-lib_schema.ValidationError.html}
|
|
224
|
+
*/
|
|
225
|
+
export function validateScript(script) {
|
|
226
|
+
return validateType(bareScriptTypes, 'BareScript', script);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Validate an expression model
|
|
232
|
+
*
|
|
233
|
+
* @param {Object} expr - The [expression model]{@link https://craigahobbs.github.io/bare-script/model/#var.vName='Expression'}
|
|
234
|
+
* @returns {Object} The validated expression model
|
|
235
|
+
* @throws [ValidationError]{@link https://craigahobbs.github.io/schema-markdown-js/module-lib_schema.ValidationError.html}
|
|
236
|
+
*/
|
|
237
|
+
export function validateExpression(expr) {
|
|
238
|
+
return validateType(bareScriptTypes, 'Expression', expr);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Lint a BareScript script model
|
|
244
|
+
*
|
|
245
|
+
* @param {Object} script - The [BareScript model]{@link https://craigahobbs.github.io/bare-script/model/#var.vName='BareScript'}
|
|
246
|
+
* @returns {string[]} The array of lint warnings
|
|
247
|
+
*/
|
|
248
|
+
export function lintScript(script) {
|
|
249
|
+
const warnings = [];
|
|
250
|
+
|
|
251
|
+
// Empty script?
|
|
252
|
+
if (script.statements.length === 0) {
|
|
253
|
+
warnings.push('Empty script');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Variable used before assignment?
|
|
257
|
+
const varAssigns = {};
|
|
258
|
+
const varUses = {};
|
|
259
|
+
getVariableAssignmentsAndUses(script.statements, varAssigns, varUses);
|
|
260
|
+
for (const varName of Object.keys(varAssigns)) {
|
|
261
|
+
if (varName in varUses && varUses[varName] <= varAssigns[varName]) {
|
|
262
|
+
warnings.push(
|
|
263
|
+
`Global variable "${varName}" used (index ${varUses[varName]}) before assignment (index ${varAssigns[varName]})`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Iterate global statements
|
|
269
|
+
const functionsDefined = {};
|
|
270
|
+
const labelsDefined = {};
|
|
271
|
+
const labelsUsed = {};
|
|
272
|
+
for (const [ixStatement, statement] of script.statements.entries()) {
|
|
273
|
+
const [statementKey] = Object.keys(statement);
|
|
274
|
+
|
|
275
|
+
// Function definition checks
|
|
276
|
+
if (statementKey === 'function') {
|
|
277
|
+
// Function redefinition?
|
|
278
|
+
if (statement.function.name in functionsDefined) {
|
|
279
|
+
warnings.push(`Redefinition of function "${statement.function.name}" (index ${ixStatement})`);
|
|
280
|
+
} else {
|
|
281
|
+
functionsDefined[statement.function.name] = ixStatement;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Variable used before assignment?
|
|
285
|
+
const fnVarAssigns = {};
|
|
286
|
+
const fnVarUses = {};
|
|
287
|
+
const args = (statement.function.args ?? null);
|
|
288
|
+
getVariableAssignmentsAndUses(statement.function.statements, fnVarAssigns, fnVarUses);
|
|
289
|
+
for (const varName of Object.keys(fnVarAssigns)) {
|
|
290
|
+
// Ignore re-assigned function arguments
|
|
291
|
+
if (args !== null && args.indexOf(varName) !== -1) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (varName in fnVarUses && fnVarUses[varName] <= fnVarAssigns[varName]) {
|
|
295
|
+
warnings.push(
|
|
296
|
+
`Variable "${varName}" of function "${statement.function.name}" used (index ${fnVarUses[varName]}) ` +
|
|
297
|
+
`before assignment (index ${fnVarAssigns[varName]})`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Unused variables?
|
|
303
|
+
for (const varName of Object.keys(fnVarAssigns)) {
|
|
304
|
+
if (!(varName in fnVarUses)) {
|
|
305
|
+
warnings.push(
|
|
306
|
+
`Unused variable "${varName}" defined in function "${statement.function.name}" (index ${fnVarAssigns[varName]})`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Function argument checks
|
|
312
|
+
if (args !== null) {
|
|
313
|
+
const argsDefined = new Set();
|
|
314
|
+
for (const arg of args) {
|
|
315
|
+
// Duplicate argument?
|
|
316
|
+
if (argsDefined.has(arg)) {
|
|
317
|
+
warnings.push(`Duplicate argument "${arg}" of function "${statement.function.name}" (index ${ixStatement})`);
|
|
318
|
+
} else {
|
|
319
|
+
argsDefined.add(arg);
|
|
320
|
+
|
|
321
|
+
// Unused argument?
|
|
322
|
+
if (!(arg in fnVarUses)) {
|
|
323
|
+
warnings.push(`Unused argument "${arg}" of function "${statement.function.name}" (index ${ixStatement})`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Iterate function statements
|
|
330
|
+
const fnLabelsDefined = {};
|
|
331
|
+
const fnLabelsUsed = {};
|
|
332
|
+
for (const [ixFnStatement, fnStatement] of statement.function.statements.entries()) {
|
|
333
|
+
const [fnStatementKey] = Object.keys(fnStatement);
|
|
334
|
+
|
|
335
|
+
// Function expression statement checks
|
|
336
|
+
if (fnStatementKey === 'expr') {
|
|
337
|
+
// Pointless function expression statement?
|
|
338
|
+
if (!('name' in fnStatement.expr) && isPointlessExpression(fnStatement.expr.expr)) {
|
|
339
|
+
warnings.push(`Pointless statement in function "${statement.function.name}" (index ${ixFnStatement})`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Function label statement checks
|
|
343
|
+
} else if (fnStatementKey === 'label') {
|
|
344
|
+
// Label redefinition?
|
|
345
|
+
if (fnStatement.label in fnLabelsDefined) {
|
|
346
|
+
warnings.push(
|
|
347
|
+
`Redefinition of label "${fnStatement.label}" in function "${statement.function.name}" (index ${ixFnStatement})`
|
|
348
|
+
);
|
|
349
|
+
} else {
|
|
350
|
+
fnLabelsDefined[fnStatement.label] = ixFnStatement;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Function jump statement checks
|
|
354
|
+
} else if (fnStatementKey === 'jump') {
|
|
355
|
+
if (!(fnStatement.jump.label in fnLabelsUsed)) {
|
|
356
|
+
fnLabelsUsed[fnStatement.jump.label] = ixFnStatement;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Unused function labels?
|
|
362
|
+
for (const label of Object.keys(fnLabelsDefined)) {
|
|
363
|
+
if (!(label in fnLabelsUsed)) {
|
|
364
|
+
warnings.push(`Unused label "${label}" in function "${statement.function.name}" (index ${fnLabelsDefined[label]})`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Unknown function labels?
|
|
369
|
+
for (const label of Object.keys(fnLabelsUsed)) {
|
|
370
|
+
if (!(label in fnLabelsDefined)) {
|
|
371
|
+
warnings.push(`Unknown label "${label}" in function "${statement.function.name}" (index ${fnLabelsUsed[label]})`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Global expression statement checks
|
|
376
|
+
} else if (statementKey === 'expr') {
|
|
377
|
+
// Pointless global expression statement?
|
|
378
|
+
if (!('name' in statement.expr) && isPointlessExpression(statement.expr.expr)) {
|
|
379
|
+
warnings.push(`Pointless global statement (index ${ixStatement})`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Global label statement checks
|
|
383
|
+
} else if (statementKey === 'label') {
|
|
384
|
+
// Label redefinition?
|
|
385
|
+
if (statement.label in labelsDefined) {
|
|
386
|
+
warnings.push(`Redefinition of global label "${statement.label}" (index ${ixStatement})`);
|
|
387
|
+
} else {
|
|
388
|
+
labelsDefined[statement.label] = ixStatement;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Global jump statement checks
|
|
392
|
+
} else if (statementKey === 'jump') {
|
|
393
|
+
if (!(statement.jump.label in labelsUsed)) {
|
|
394
|
+
labelsUsed[statement.jump.label] = ixStatement;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Unused global labels?
|
|
400
|
+
for (const label of Object.keys(labelsDefined)) {
|
|
401
|
+
if (!(label in labelsUsed)) {
|
|
402
|
+
warnings.push(`Unused global label "${label}" (index ${labelsDefined[label]})`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Unknown global labels?
|
|
407
|
+
for (const label of Object.keys(labelsUsed)) {
|
|
408
|
+
if (!(label in labelsDefined)) {
|
|
409
|
+
warnings.push(`Unknown global label "${label}" (index ${labelsUsed[label]})`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return warnings;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
// Helper function to determine if an expression statement's expression is pointless
|
|
418
|
+
function isPointlessExpression(expr) {
|
|
419
|
+
const [exprKey] = Object.keys(expr);
|
|
420
|
+
if (exprKey === 'function') {
|
|
421
|
+
return false;
|
|
422
|
+
} else if (exprKey === 'binary') {
|
|
423
|
+
return isPointlessExpression(expr.binary.left) && isPointlessExpression(expr.binary.right);
|
|
424
|
+
} else if (exprKey === 'unary') {
|
|
425
|
+
return isPointlessExpression(expr.unary.expr);
|
|
426
|
+
} else if (exprKey === 'group') {
|
|
427
|
+
return isPointlessExpression(expr.group);
|
|
428
|
+
}
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
// Helper function to set variable assignments/uses for a statements array
|
|
434
|
+
function getVariableAssignmentsAndUses(statements, assigns, uses) {
|
|
435
|
+
for (const [ixStatement, statement] of statements.entries()) {
|
|
436
|
+
const [statementKey] = Object.keys(statement);
|
|
437
|
+
if (statementKey === 'expr') {
|
|
438
|
+
if ('name' in statement.expr) {
|
|
439
|
+
if (!(statement.expr.name in assigns)) {
|
|
440
|
+
assigns[statement.expr.name] = ixStatement;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
getExpressionVariableUses(statement.expr.expr, uses, ixStatement);
|
|
444
|
+
} else if (statementKey === 'jump' && 'expr' in statement.jump) {
|
|
445
|
+
getExpressionVariableUses(statement.jump.expr, uses, ixStatement);
|
|
446
|
+
} else if (statementKey === 'return' && 'expr' in statement.return) {
|
|
447
|
+
getExpressionVariableUses(statement.return.expr, uses, ixStatement);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
// Helper function to set variable uses for an expression
|
|
454
|
+
function getExpressionVariableUses(expr, uses, ixStatement) {
|
|
455
|
+
const [exprKey] = Object.keys(expr);
|
|
456
|
+
if (exprKey === 'variable') {
|
|
457
|
+
if (!(expr.variable in uses)) {
|
|
458
|
+
uses[expr.variable] = ixStatement;
|
|
459
|
+
}
|
|
460
|
+
} else if (exprKey === 'binary') {
|
|
461
|
+
getExpressionVariableUses(expr.binary.left, uses, ixStatement);
|
|
462
|
+
getExpressionVariableUses(expr.binary.right, uses, ixStatement);
|
|
463
|
+
} else if (exprKey === 'unary') {
|
|
464
|
+
getExpressionVariableUses(expr.unary.expr, uses, ixStatement);
|
|
465
|
+
} else if (exprKey === 'group') {
|
|
466
|
+
getExpressionVariableUses(expr.group, uses, ixStatement);
|
|
467
|
+
} else if (exprKey === 'function') {
|
|
468
|
+
if (!(expr.function.name in uses)) {
|
|
469
|
+
uses[expr.function.name] = ixStatement;
|
|
470
|
+
}
|
|
471
|
+
if ('args' in expr.function) {
|
|
472
|
+
for (const argExpr of expr.function.args) {
|
|
473
|
+
getExpressionVariableUses(argExpr, uses, ixStatement);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|