GCC Code Coverage Report


Directory: avs_core/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 24.8% 127 / 0 / 512
Functions: 70.8% 17 / 0 / 24
Branches: 8.5% 113 / 0 / 1328

core/parser/scriptparser.cpp
Line Branch Exec Source
1 // Avisynth v2.5. Copyright 2002 Ben Rudiak-Gould et al.
2 // http://avisynth.nl
3
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or visit
17 // http://www.gnu.org/copyleft/gpl.html .
18 //
19 // Linking Avisynth statically or dynamically with other modules is making a
20 // combined work based on Avisynth. Thus, the terms and conditions of the GNU
21 // General Public License cover the whole combination.
22 //
23 // As a special exception, the copyright holders of Avisynth give you
24 // permission to link Avisynth with independent modules that communicate with
25 // Avisynth solely through the interfaces defined in avisynth.h, regardless of the license
26 // terms of these independent modules, and to copy and distribute the
27 // resulting combined work under terms of your choice, provided that
28 // every copy of the combined work is accompanied by a complete copy of
29 // the source code of Avisynth (the version of Avisynth used to produce the
30 // combined work), being distributed under the terms of the GNU General
31 // Public License plus this exception. An independent module is a module
32 // which is not derived from or based on Avisynth, such as 3rd-party filters,
33 // import and export plugins, or graphical user interfaces.
34
35
36 #include "scriptparser.h"
37 #include "../InternalEnvironment.h"
38
39
40 /********************************
41 ******* Script Parser ******
42 *******************************/
43
44
45 6 ScriptParser::ScriptParser(IScriptEnvironment* _env, const char* _code, const char* _filename)
46 6 : env(static_cast<IScriptEnvironment2*>(_env)), tokenizer(_code, _env), code(_code), filename(_filename), loopDepth(0) {}
47
48 6 PExpression ScriptParser::Parse(void)
49 {
50 try {
51
5/12
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 19 not taken.
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 15 not taken.
✓ Branch 4 → 5 taken 6 times.
✗ Branch 4 → 13 not taken.
✓ Branch 5 → 6 taken 6 times.
✗ Branch 5 → 13 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 6 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
12 return new ExpRootBlock(ParseBlock(false, NULL));
52 }
53 catch (const AvisynthError &ae) {
54 env->ThrowError("%s\n(%s, line %d, column %d)", ae.msg, filename, tokenizer.GetLine(), tokenizer.GetColumn(code));
55 }
56 #ifndef _DEBUG
57 catch (...) {
58 env->ThrowError("Parse: Unrecognized exception!");
59 }
60 #endif
61 return 0; // To make VC++ happy. Why isn't the __declspec(noreturn) on ThrowError good enough?
62 }
63
64
65 void ScriptParser::Expect(int op, const char* msg=0)
66 {
67 if (tokenizer.IsOperator(op))
68 tokenizer.NextToken();
69 else {
70 if (msg)
71 env->ThrowError(msg);
72 else {
73 if (op < 256)
74 env->ThrowError("Script error: expected `%c'", op);
75 else
76 env->ThrowError("Script error: expected `%c%c'", (op>>8), (op&255));
77 }
78 }
79 }
80
81
82 PExpression ScriptParser::ParseFunctionDefinition(void)
83 {
84 /*
85 bool global_spcified = false;
86 if (tokenizer.IsIdentifier("global")) {
87 tokenizer.NextToken();
88
89 }
90 */
91 const char* name = nullptr;
92 if (tokenizer.IsIdentifier()) {
93 name = tokenizer.AsIdentifier();
94 tokenizer.NextToken();
95 }
96 const char* var_names[max_args];
97 int var_count = 0;
98 char param_types[4096];
99 int param_chars=0;
100 bool param_floats[max_args];
101 const char* param_names[max_args];
102 int param_count=0;
103
104 // variable capture
105 if (tokenizer.IsOperator('[')) {
106 if (name != nullptr) {
107 env->ThrowError("Script error: variable capture is not supported on legacy function definition.");
108 }
109 tokenizer.NextToken();
110 bool need_comma = false;
111 for (;;) {
112 if (tokenizer.IsOperator(']')) {
113 tokenizer.NextToken();
114 break;
115 }
116 if (need_comma) {
117 Expect(',', "Script error: expected a , or ]");
118 }
119
120 if (tokenizer.IsIdentifier()) {
121 var_names[var_count++] = tokenizer.AsIdentifier();
122 }
123 else {
124 env->ThrowError("Script error: expected a parameter name");
125 }
126
127 tokenizer.NextToken();
128 need_comma = true;
129 }
130 }
131
132 if (!tokenizer.IsOperator('{')) {
133 Expect('(', "Script error: expected ( or { after function name");
134 bool need_comma = false;
135 bool named_arg_found = false;
136 for (;;) {
137 if (tokenizer.IsOperator(')')) {
138 tokenizer.NextToken();
139 break;
140 }
141 if (need_comma) {
142 Expect(',', "Script error: expected a , or )");
143 }
144 if (param_count == max_args) {
145 env->ThrowError("Script error: parameter list too long");
146 }
147
148 param_floats[param_count] = false;
149 char type = '.';
150 char array_kind = ' '; // can be * (zero_or_more) or + (one_or_more)
151 Tokenizer lookahead(&tokenizer);
152 if (lookahead.IsIdentifier() || lookahead.IsString()) {
153 // we have a variable type preceding its name
154 if (tokenizer.IsIdentifier("val")) type = '.';
155 else if (tokenizer.IsIdentifier("bool")) type = 'b';
156 else if (tokenizer.IsIdentifier("int")) type = 'i'; // also accepts 64 bit long since v11
157 else if (tokenizer.IsIdentifier("float")) {
158 param_floats[param_count] = true;
159 type = 'f'; // also accepts doubles since v11
160 }
161 else if (tokenizer.IsIdentifier("string")) type = 's';
162 else if (tokenizer.IsIdentifier("clip")) type = 'c';
163 else if (tokenizer.IsIdentifier("func")) type = 'n';
164 // AVS+ 161028 array type in user defined functions
165 else if (tokenizer.IsIdentifier("array") || tokenizer.IsIdentifier("val_array")) {
166 array_kind = '*'; type = '.';
167 // or: isArray = false; type = 'a'; ? No. Keeping the old syntax
168 // but .+ must be the very last parameter if parameter is unnamed
169 }
170 else if (tokenizer.IsIdentifier("array_nz") || tokenizer.IsIdentifier("val_array_nz")) {
171 array_kind = '+'; type = '.'; // _nz: non-zero size
172 }
173 else if (tokenizer.IsIdentifier("bool_array")) {
174 array_kind = '*'; type = 'b';
175 }
176 else if (tokenizer.IsIdentifier("bool_array_nz")) {
177 array_kind = '+'; type = 'b';
178 }
179 else if (tokenizer.IsIdentifier("int_array")) {
180 array_kind = '*'; type = 'i';
181 }
182 else if (tokenizer.IsIdentifier("int_array_nz")) {
183 array_kind = '+'; type = 'i';
184 }
185 else if (tokenizer.IsIdentifier("float_array")) {
186 array_kind = '*'; type = 'f';
187 }
188 else if (tokenizer.IsIdentifier("float_array_nz")) {
189 array_kind = '+'; type = 'f';
190 }
191 else if (tokenizer.IsIdentifier("string_array")) {
192 array_kind = '*'; type = 's';
193 }
194 else if (tokenizer.IsIdentifier("string_array_nz")) {
195 array_kind = '+'; type = 's';
196 }
197 else if (tokenizer.IsIdentifier("clip_array")) {
198 array_kind = '*'; type = 'c';
199 }
200 else if (tokenizer.IsIdentifier("clip_array_nz")) {
201 array_kind = '+'; type = 'c';
202 }
203 else if (tokenizer.IsIdentifier("func_array")) {
204 array_kind = '*'; type = 'n';
205 }
206 else if (tokenizer.IsIdentifier("func_array_nz")) {
207 array_kind = '+'; type = 'n';
208 }
209 else env->ThrowError("Script error: expected \"val\", \"bool\", \"int\", \"float\", \"string\", \"array\", or \"clip\" (or their \"_array\" or \"_array_nz\" versions");
210 tokenizer.NextToken();
211 }
212
213 if (tokenizer.IsIdentifier()) {
214 if (named_arg_found)
215 env->ThrowError("Script error: can't have a named (quoted) parameter followed by an ordinary parameter");
216 param_names[param_count++] = tokenizer.AsIdentifier();
217 } else if (tokenizer.IsString()) {
218 named_arg_found = true;
219 const char* param_name = param_names[param_count++] = tokenizer.AsString();
220 int len = lstrlen(param_name);
221 if (param_chars + lstrlen(param_name) >= 4000)
222 env->ThrowError("Script error: parameter list too long");
223 param_types[param_chars] = '[';
224 memcpy(&param_types[param_chars+1], param_name, len);
225 param_types[param_chars+len+1] = ']';
226 param_chars += len+2;
227 } else {
228 env->ThrowError("Script error: expected a parameter name");
229 }
230 param_types[param_chars++] = type;
231 if(array_kind == '*' || array_kind == '+')
232 param_types[param_chars++] = array_kind; // zero or more / one or more
233 tokenizer.NextToken();
234
235 need_comma = true;
236 }
237 }
238
239 int line = tokenizer.GetLine();
240
241 param_types[param_chars] = 0;
242 PExpression body = new ExpRootBlock(ParseBlock(true, NULL));
243
244 const char* saved_param_signature = env->SaveString(param_types);
245
246 if(name != nullptr) {
247 // legacy function definition
248 ScriptFunction* sf = new ScriptFunction(body, param_floats, param_names, param_count);
249 env->AtExit(ScriptFunction::Delete, sf);
250 env->AddFunction(name, saved_param_signature, ScriptFunction::Execute, sf, "$UserFunctions$");
251 return new ExpLegacyFunctionDefinition();
252 }
253
254 if (name) {
255 auto envi = static_cast<InternalEnvironment*>(env);
256 envi->UpdateFunctionExports(name, saved_param_signature, "$UserFunctions$");
257 }
258
259 return new ExpFunctionDefinition(body, name, saved_param_signature,
260 param_floats, param_names, param_count, var_names, var_count,
261 filename, line);
262 // was before 20200324:
263 // bool is_global = global_spcified || (var_count == 0);
264 // return new ExpFunctionDefinition(name, sf, is_global);
265 }
266
267
268 6 PExpression ScriptParser::ParseBlock(bool braced, bool *empty)
269 {
270
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 8 taken 6 times.
6 if (braced) {
271 // allow newlines (and hence comments) before '{' -- Gavino 7 Dec 2009
272 while (tokenizer.IsNewline())
273 tokenizer.NextToken();
274
275 Expect('{');
276 }
277
278 // the purpose of this array and the accompanying code is to produce
279 // a nice balanced binary tree of ExpSequence objects, so that the
280 // maximum call depth in Evaluate grows logarithmically instead of
281 // linearly.
282 // For every i, either trees[i]==0 or it's a balanced tree of (1<<i) elts.
283
3/8
✓ Branch 9 → 10 taken 120 times.
✗ Branch 9 → 115 not taken.
✓ Branch 11 → 9 taken 120 times.
✓ Branch 11 → 12 taken 6 times.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 119 not taken.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 119 not taken.
252 PExpression trees[20];
284
285 6 bool ignore_remainder = false;
286 for (;;) {
287
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 12 times.
12 if (tokenizer.IsNewline()) {
288 tokenizer.NextToken();
289
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 22 taken 12 times.
12 } else if (tokenizer.IsOperator('}')) {
290 if (braced) {
291 tokenizer.NextToken();
292 break;
293 } else {
294 env->ThrowError("Script error: found } without a matching {");
295 }
296
2/2
✓ Branch 23 → 24 taken 6 times.
✓ Branch 23 → 27 taken 6 times.
12 } else if (tokenizer.IsEOF()) {
297
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 6 times.
6 if (braced) {
298 env->ThrowError("Script error: end of file reached without matching }");
299 } else {
300 6 break;
301 }
302 } else {
303 bool stop;
304
1/2
✓ Branch 27 → 28 taken 6 times.
✗ Branch 27 → 128 not taken.
6 PExpression exp = ParseStatement(&stop);
305
3/6
✓ Branch 28 → 29 taken 6 times.
✗ Branch 28 → 32 not taken.
✓ Branch 30 → 31 taken 6 times.
✗ Branch 30 → 32 not taken.
✓ Branch 33 → 34 taken 6 times.
✗ Branch 33 → 40 not taken.
6 if (!braced && exp) {
306
1/2
✓ Branch 35 → 36 taken 6 times.
✗ Branch 35 → 37 not taken.
6 auto ep = dynamic_cast<ExpLegacyFunctionDefinition*>((Expression*)(void*)exp);
307
1/2
✗ Branch 38 → 39 not taken.
✓ Branch 38 → 40 taken 6 times.
6 if (ep != nullptr) {
308 // Inhibit legacy function definition to get into expression tree.
309 // Or else "return last" would be needed before them.
310 // We check actual type runtime with dynamic cast.
311 exp = nullptr;
312 }
313 }
314
3/6
✓ Branch 41 → 42 taken 6 times.
✗ Branch 41 → 44 not taken.
✓ Branch 42 → 43 taken 6 times.
✗ Branch 42 → 44 not taken.
✓ Branch 45 → 46 taken 6 times.
✗ Branch 45 → 68 not taken.
6 if (exp && !ignore_remainder) {
315
1/2
✓ Branch 46 → 47 taken 6 times.
✗ Branch 46 → 54 not taken.
6 if (filename)
316
4/10
✓ Branch 47 → 48 taken 6 times.
✗ Branch 47 → 126 not taken.
✓ Branch 49 → 50 taken 6 times.
✗ Branch 49 → 120 not taken.
✓ Branch 50 → 51 taken 6 times.
✗ Branch 50 → 120 not taken.
✗ Branch 51 → 52 not taken.
✓ Branch 51 → 53 taken 6 times.
✗ Branch 120 → 121 not taken.
✗ Branch 120 → 122 not taken.
6 exp = new ExpLine(exp, filename, tokenizer.GetLine());
317
1/2
✓ Branch 66 → 55 taken 6 times.
✗ Branch 66 → 67 not taken.
6 for (int i=0; i<20; ++i) {
318
1/2
✗ Branch 56 → 57 not taken.
✓ Branch 56 → 63 taken 6 times.
6 if (trees[i]) {
319 exp = new ExpSequence(trees[i], exp);
320 trees[i] = 0;
321 } else {
322
1/2
✓ Branch 63 → 64 taken 6 times.
✗ Branch 63 → 126 not taken.
6 trees[i] = exp;
323 6 break;
324 }
325 }
326 }
327 6 ignore_remainder |= stop;
328 6 }
329 6 }
330
331
1/2
✓ Branch 71 → 72 taken 6 times.
✗ Branch 71 → 144 not taken.
6 PExpression result = trees[0];
332
2/2
✓ Branch 88 → 73 taken 114 times.
✓ Branch 88 → 89 taken 6 times.
120 for (int i=1; i<20; ++i) {
333
1/2
✗ Branch 74 → 75 not taken.
✓ Branch 74 → 87 taken 114 times.
114 if (trees[i])
334 result = result ? PExpression(new ExpSequence(trees[i], result)) : trees[i];
335 }
336
337
1/2
✓ Branch 90 → 91 taken 6 times.
✗ Branch 90 → 95 not taken.
6 if (result)
338 {
339
1/2
✗ Branch 91 → 92 not taken.
✓ Branch 91 → 93 taken 6 times.
6 if (empty) *empty = false;
340
1/2
✓ Branch 93 → 94 taken 6 times.
✗ Branch 93 → 142 not taken.
6 return result;
341 }
342 else
343 {
344 if (empty) *empty = true;
345 return PExpression(new ExpConstant(AVSValue()));
346 }
347
2/4
✓ Branch 108 → 109 taken 120 times.
✓ Branch 108 → 112 taken 6 times.
✗ Branch 145 → 146 not taken.
✗ Branch 145 → 149 not taken.
132 }
348
349
350
351 6 PExpression ScriptParser::ParseStatement(bool* stop)
352 {
353 6 *stop = false;
354 // null statement
355
3/6
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 6 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 6 times.
6 if (tokenizer.IsNewline() || tokenizer.IsEOF()) {
356 return 0;
357 }
358 /*
359 // original AVS2.6/old Avs+: function definition returns 0
360 // Function declaration moved more inner
361 // Since Neo/3.6.0 we have function objects besides script functions.
362 // Thus return 0 is conditional and handled outside.
363 else if (tokenizer.IsIdentifier("function")) {
364 tokenizer.NextToken();
365 ParseFunctionDefinition();
366 return 0;
367 }
368 */
369 // exception handling
370
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 40 taken 6 times.
6 else if (tokenizer.IsIdentifier("try")) {
371 tokenizer.NextToken();
372 PExpression try_block = ParseBlock(true, NULL);
373 while (tokenizer.IsNewline())
374 tokenizer.NextToken();
375 if (!tokenizer.IsIdentifier("catch"))
376 env->ThrowError("Script error: expected `catch'");
377 tokenizer.NextToken();
378 Expect('(');
379 if (!tokenizer.IsIdentifier())
380 env->ThrowError("Script error: expected identifier");
381 const char* id = tokenizer.AsIdentifier();
382 tokenizer.NextToken();
383 Expect(')');
384 return new ExpTryCatch(try_block, id, ParseBlock(true, NULL));
385 }
386 // 'if', 'while', 'for':
387
1/2
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 6 times.
6 else if (tokenizer.IsIdentifier("if")) {
388 return ParseIf();
389 }
390
1/2
✗ Branch 44 → 45 not taken.
✓ Branch 44 → 46 taken 6 times.
6 else if (tokenizer.IsIdentifier("while")) {
391 return ParseWhile();
392 }
393
1/2
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 6 times.
6 else if (tokenizer.IsIdentifier("for")) {
394 return ParseFor();
395 }
396 // return statement
397
1/2
✗ Branch 50 → 51 not taken.
✓ Branch 50 → 61 taken 6 times.
6 else if (tokenizer.IsIdentifier("return")) {
398 *stop = true;
399 tokenizer.NextToken();
400 return new ExpReturn(ParseAssignmentWithRet());
401 }
402 // break statement
403
1/2
✗ Branch 62 → 63 not taken.
✓ Branch 62 → 74 taken 6 times.
6 else if (tokenizer.IsIdentifier("break")) {
404 if (loopDepth <= 0)
405 throw AvisynthError("'Break' statement outside of loop.");
406 tokenizer.NextToken();
407 return new ExpBreak();
408 }
409 // continue statement
410
1/2
✗ Branch 75 → 76 not taken.
✓ Branch 75 → 87 taken 6 times.
6 else if (tokenizer.IsIdentifier("continue")) {
411 if (loopDepth <= 0)
412 throw AvisynthError("'Continue' statement outside of loop.");
413 tokenizer.NextToken();
414 return new ExpContinue();
415 }
416 else {
417 6 return ParseAssignment();
418 }
419 }
420
421 PExpression ScriptParser::ParseIf(void)
422 {
423 bool blockEmpty;
424
425 PExpression If, Then, Else = 0;
426 tokenizer.NextToken();
427 Expect('(');
428 If = ParseAssignmentWithRet();
429 Expect(')');
430
431 Then = ParseBlock(true, &blockEmpty);
432 if (blockEmpty)
433 Then = NULL;
434
435 while (tokenizer.IsNewline())
436 tokenizer.NextToken();
437 if (tokenizer.IsIdentifier("else")) {
438 tokenizer.NextToken();
439
440 if (tokenizer.IsIdentifier("if"))
441 {
442 Else = ParseIf();
443 }
444 else
445 {
446 Else = ParseBlock(true, &blockEmpty);
447 if (blockEmpty)
448 Else = NULL;
449 }
450 }
451 return new ExpBlockConditional(If, Then, Else);
452 }
453
454 PExpression ScriptParser::ParseWhile(void)
455 {
456 tokenizer.NextToken();
457 Expect('(');
458 const PExpression cond = ParseAssignmentWithRet();
459 Expect(')');
460
461 ++loopDepth;
462 bool blockEmpty;
463 PExpression body = ParseBlock(true, &blockEmpty);
464 if (blockEmpty)
465 body = NULL;
466 --loopDepth;
467
468 return new ExpWhileLoop(cond, body);
469 }
470
471 PExpression ScriptParser::ParseFor(void)
472 {
473 tokenizer.NextToken();
474 Expect('(');
475 if (!tokenizer.IsIdentifier())
476 env->ThrowError("Script error: expected a variable name");
477 const char* id = tokenizer.AsIdentifier();
478 tokenizer.NextToken();
479 Expect('=');
480 const PExpression init = ParseAssignmentWithRet();
481 Expect(',');
482 const PExpression limit = ParseAssignmentWithRet();
483 PExpression step = NULL;
484 if (tokenizer.IsOperator(',')) {
485 tokenizer.NextToken();
486 step = ParseAssignmentWithRet();
487 } else {
488 step = PExpression(new ExpConstant(AVSValue(1)));
489 }
490
491 Expect(')');
492
493 ++loopDepth;
494 bool blockEmpty;
495 PExpression body = ParseBlock(true, &blockEmpty);
496 if (blockEmpty)
497 body = NULL;
498 --loopDepth;
499
500 return new ExpForLoop(id, init, limit, step, body);
501 }
502
503 6 PExpression ScriptParser::ParseAssignment(void)
504 {
505
2/4
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 55 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 19 taken 6 times.
6 if (tokenizer.IsIdentifier("global")) {
506 tokenizer.NextToken();
507 if (!tokenizer.IsIdentifier())
508 env->ThrowError("Script error: `global' must be followed by a variable name");
509 const char* name = tokenizer.AsIdentifier();
510 tokenizer.NextToken();
511 Expect('=');
512 PExpression exp = ParseConditional();
513 return new ExpGlobalAssignment(name, exp);
514 }
515
1/2
✓ Branch 19 → 20 taken 6 times.
✗ Branch 19 → 55 not taken.
6 PExpression exp = ParseAssignmentWithRet();
516
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 36 taken 6 times.
6 if (tokenizer.IsOperator('=')) {
517 const char* name = exp->GetLvalue();
518 if (!name)
519 env->ThrowError("Script error: left operand of `=' must be a variable name");
520 tokenizer.NextToken();
521 exp = ParseAssignmentWithRet();
522 return new ExpAssignment(name, exp);
523 }
524
525
1/2
✓ Branch 36 → 37 taken 6 times.
✗ Branch 36 → 53 not taken.
6 return exp;
526 6 }
527
528 6 PExpression ScriptParser::ParseAssignmentWithRet(void)
529 {
530
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 33 not taken.
6 PExpression exp = ParseConditional();
531
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 20 taken 6 times.
6 if (tokenizer.IsOperator(":="_i)) {
532 const char* name = exp->GetLvalue();
533 if (!name)
534 env->ThrowError("Script error: left operand of `:=' must be a variable name");
535 tokenizer.NextToken();
536 exp = ParseAssignmentWithRet();
537 return new ExpAssignment(name, exp, true);
538 }
539
1/2
✓ Branch 20 → 21 taken 6 times.
✗ Branch 20 → 31 not taken.
6 return exp;
540 6 }
541
542 6 PExpression ScriptParser::ParseConditional(void)
543 {
544
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 32 not taken.
6 PExpression a = ParseOr();
545
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 17 taken 6 times.
6 if (tokenizer.IsOperator('?')) {
546 tokenizer.NextToken();
547 PExpression b = ParseAssignmentWithRet();
548 Expect(':');
549 PExpression c = ParseAssignmentWithRet();
550 return new ExpConditional(a, b, c);
551 }
552
1/2
✓ Branch 17 → 18 taken 6 times.
✗ Branch 17 → 30 not taken.
6 return a;
553 6 }
554
555 6 PExpression ScriptParser::ParseOr(void)
556 {
557
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 28 not taken.
6 PExpression left = ParseAnd();
558
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 15 taken 6 times.
6 if (tokenizer.IsOperator("||"_i)) {
559 tokenizer.NextToken();
560 PExpression right = ParseOr();
561 return new ExpOr(left, right);
562 }
563
1/2
✓ Branch 15 → 16 taken 6 times.
✗ Branch 15 → 26 not taken.
6 return left;
564 6 }
565
566 6 PExpression ScriptParser::ParseAnd(void)
567 {
568
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 28 not taken.
6 PExpression left = ParseComparison();
569
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 15 taken 6 times.
6 if (tokenizer.IsOperator("&&"_i)) {
570 tokenizer.NextToken();
571 PExpression right = ParseAnd();
572 return new ExpAnd(left, right);
573 }
574
1/2
✓ Branch 15 → 16 taken 6 times.
✗ Branch 15 → 26 not taken.
6 return left;
575 6 }
576
577
578 6 PExpression ScriptParser::ParseComparison(void)
579 {
580
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 163 not taken.
6 PExpression left = ParseAddition(false);
581
1/2
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 161 not taken.
6 PExpression result;
582 int op;
583
2/4
✓ Branch 90 → 91 taken 6 times.
✗ Branch 90 → 159 not taken.
✗ Branch 91 → 5 not taken.
✓ Branch 91 → 92 taken 6 times.
6 while ((op = GetTokenAsComparisonOperator()) != 0) {
584 tokenizer.NextToken();
585 PExpression right = ParseAddition(false);
586 PExpression term;
587 switch (op) {
588 case "=="_i: term = new ExpEqual(left, right); break;
589 case "!="_i: term = new ExpNot(new ExpEqual(left, right)); break;
590 case "<>"_i: term = new ExpNot(new ExpEqual(left, right)); break;
591 case '<': term = new ExpLess(left, right); break;
592 case ">="_i: term = new ExpNot(new ExpLess(left, right)); break;
593 case '>': term = new ExpLess(right, left); break;
594 case "<="_i: term = new ExpNot(new ExpLess(right, left)); break;
595 }
596 result = !result ? term : PExpression(new ExpAnd(result, term));
597 left = right;
598 }
599
2/4
✗ Branch 93 → 94 not taken.
✓ Branch 93 → 95 taken 6 times.
✓ Branch 96 → 97 taken 6 times.
✗ Branch 96 → 159 not taken.
12 return result ? result : left;
600 6 }
601
602
603
604 6 PExpression ScriptParser::ParseAddition(bool negationOnHold) //update exterior calls to ParseAddition(false)
605 {
606
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 43 not taken.
6 PExpression left = ParseMultiplication(negationOnHold);
607 6 bool plus = tokenizer.IsOperator('+');
608 6 bool minus = tokenizer.IsOperator('-');
609 6 bool doubleplus = tokenizer.IsOperator("++"_i);
610
3/6
✓ Branch 7 → 8 taken 6 times.
✗ Branch 7 → 10 not taken.
✓ Branch 8 → 9 taken 6 times.
✗ Branch 8 → 10 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 27 taken 6 times.
6 if (plus || minus || doubleplus) {
611 tokenizer.NextToken();
612 PExpression right = ParseAddition(minus);
613 if (doubleplus) {
614 return new ExpDoublePlus(left, right);
615 }
616 return new ExpPlus(left, right); //no longer ExpMinus 'right' will be negated when needed
617 }
618
1/2
✓ Branch 27 → 28 taken 6 times.
✗ Branch 27 → 41 not taken.
6 return left;
619 6 }
620
621 6 PExpression ScriptParser::ParseMultiplication(bool negationOnHold)
622 {
623 6 PExpression left = ParseUnary();
624
625 for (;;) {
626 6 bool mult = tokenizer.IsOperator('*');
627 6 bool div = tokenizer.IsOperator('/');
628 6 bool mod = tokenizer.IsOperator('%');
629
630
3/6
✓ Branch 6 → 7 taken 6 times.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 8 taken 6 times.
✗ Branch 7 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 32 taken 6 times.
6 if (mult || div || mod)
631 tokenizer.NextToken();
632 else break; //exits the while if not a mult op
633
634 PExpression right = ParseUnary();
635 if (mult)
636 left = new ExpMult(left, right);
637 else if (div)
638 left = new ExpDiv(left, right);
639 else
640 left = new ExpMod(left, right);
641 }
642
643
1/2
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 39 taken 6 times.
6 if (negationOnHold) //negate the factorised result if needed
644 left = new ExpNegate(left);
645 6 return left;
646 }
647
648
649 6 PExpression ScriptParser::ParseUnary(void) {
650 // accept '+' with anything
651
1/2
✗ Branch 5 → 3 not taken.
✓ Branch 5 → 6 taken 6 times.
6 while (tokenizer.IsOperator('+'))
652 tokenizer.NextToken();
653
654
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 18 taken 6 times.
6 if (tokenizer.IsOperator('-')) {
655 tokenizer.NextToken();
656 return new ExpNegate(ParseUnary());
657 }
658
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 30 taken 6 times.
6 else if (tokenizer.IsOperator('!')) {
659 tokenizer.NextToken();
660 return new ExpNot(ParseUnary());
661 }
662 else {
663 6 return ParseOOP();
664 }
665 }
666
667 6 PExpression ScriptParser::ParseOOP(void)
668 {
669 // check if need convert [] brackets to function calls: Array() and ArrayGet()
670 // no context and [: e.g. x = [23,2] --> x = Array(23,2)
671 6 PExpression left;
672
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 11 taken 6 times.
6 if (tokenizer.IsOperator('['))
673 left = ParseArray(nullptr);
674 else
675
3/6
✓ Branch 11 → 12 taken 6 times.
✗ Branch 11 → 53 not taken.
✓ Branch 12 → 13 taken 6 times.
✗ Branch 12 → 51 not taken.
✓ Branch 13 → 14 taken 6 times.
✗ Branch 13 → 49 not taken.
6 left = ParseFunction(nullptr);
676
3/6
✓ Branch 35 → 36 taken 6 times.
✗ Branch 35 → 38 not taken.
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 39 taken 6 times.
✗ Branch 40 → 18 not taken.
✓ Branch 40 → 41 taken 6 times.
6 while (tokenizer.IsOperator('.') || tokenizer.IsOperator('[')) {
677 // OOP '.' or array indexing
678 // somename.function( ...
679 // somename[ ... : e.g. x = a[23,2] --> x = a.ArrayGet(23,2)
680 // context is 'somename'
681 const bool isArraySpecifier = tokenizer.IsOperator('[');
682 tokenizer.NextToken();
683 if(isArraySpecifier)
684 left = ParseArray(left);
685 else
686 left = ParseFunction(left);
687 }
688 6 return left;
689 }
690
691 PExpression ScriptParser::ParseArray(PExpression context)
692 {
693 PExpression left;
694 if (context)
695 left = new ExpVariableReference("ArrayGet"); // x = a[2,1] -> x = a.ArrayGet(2,1)
696 else
697 left = new ExpVariableReference("Array"); // x = [23,3] --> x = Array(23,3)
698 PExpression result = ParseCall(left, context, true); // true: array syntax
699 return result;
700 }
701
702 6 PExpression ScriptParser::ParseFunction(PExpression context)
703 {
704
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 30 not taken.
6 PExpression left = ParseAtom();
705
3/6
✓ Branch 4 → 5 taken 6 times.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 6 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 17 taken 6 times.
6 if (context || tokenizer.IsOperator('(')) {
706 return ParseCall(left, context, false); // false: function syntax
707 }
708
1/2
✓ Branch 17 → 18 taken 6 times.
✗ Branch 17 → 28 not taken.
6 return left;
709 6 }
710
711 /*
712 -NEW_AVSVALUE means arrays
713 - AVSValue deep copy for arrays (arrays in arrays in ...)
714 constant script arrays
715 array_variable = [[1, 2, 3], [4, 5, 8], "hello"]
716 dictionary = [["one", 1], ["two", 2]]
717 empty = []
718 subarray = array_variable[0]
719 val = subarray[2]
720 val2 = array_variable[1, 3]
721 str = array_variable[2]
722 n = ArraySize(array_variable) #3
723 n2 = ArraySize(empty) #0
724 val3 = dictionary["two"]
725
726 - arrays as filter parameters(named and unnamed) :
727 new 'a' type or use '.+' or '.*' and check AVSValue IsArray()
728 Concept is incompatible with avs 2.5 plugins due to their 'baked' interface code
729 */
730 PExpression ScriptParser::ParseCall(PExpression left, PExpression context, bool isArraySpecifier)
731 {
732 // though parser can do here arrays in general, isArraySpecifier is always false for non AVS_NEWVALUE
733 // array references are using [] brackets which are here
734 // converted to Array() and ArrayGet functions depending on the position
735 // x = a[23] --> x = a.ArrayGet(23) has a variable reference (context is not null)
736 // x = [2,3] -> x = Array(2,3) no context
737
738 // function
739 PExpression args[max_args];
740 const char* arg_names[max_args];
741 memset(arg_names, 0, sizeof(arg_names));
742 int params_count = 0;
743 int i=0;
744 if (context)
745 args[i++] = context; // first arg is the object before '.' or '['
746
747 if (
748 isArraySpecifier ||
749 tokenizer.IsOperator('(')) {
750
751 if(!(context != nullptr && isArraySpecifier)) // ParseOOP already had [ and also the next token
752 tokenizer.NextToken();
753
754 const char end_bracket = isArraySpecifier ? ']' : ')';
755 // arrays are delimited by ], functions by )
756
757 bool need_comma = false;
758 for (;;) {
759 if(tokenizer.IsOperator(end_bracket))
760 {
761 tokenizer.NextToken();
762 break;
763 }
764 if (need_comma) {
765 if(isArraySpecifier)
766 Expect(',', "Script error: expected a , or ]");
767 else
768 Expect(',', "Script error: expected a , or )");
769 }
770 // check for named argument syntax (name=val) for functions
771 if (!isArraySpecifier && tokenizer.IsIdentifier()) {
772 Tokenizer lookahead(&tokenizer);
773 if (lookahead.IsOperator('=')) {
774 arg_names[i] = tokenizer.AsIdentifier();
775 tokenizer.NextToken();
776 tokenizer.NextToken();
777 }
778 }
779 if (i == max_args) {
780 env->ThrowError("Script error: argument list too long");
781 }
782 args[i++] = ParseAssignmentWithRet();
783 params_count++;
784 need_comma = true;
785 }
786 }
787
788 if (isArraySpecifier && params_count == 0)
789 {
790 if (context != nullptr)
791 env->ThrowError("Script error: array indexing must have at least one index");
792 // contextless case is empty array definition: x = []
793 }
794
795 const char* name = left->GetLvalue();
796 if (name && i == 1 && args[0]->GetLvalue() && lstrcmpi(name, "func") == 0) {
797 // special case the parser should deal with
798 // "func(LegacyFunctionName)"
799 left = new ExpFunctionWrapper(args[0]->GetLvalue());
800 }
801 else {
802 // we have to set this true for array definitions x = [2,3] or else an
803 // implicite "last" will be passed on by ExpFunctionCall::Evaluate
804 const bool oop_notation_aka_disable_implicite_last = !!context || isArraySpecifier;
805 left = new ExpFunctionCall(name, left, args, arg_names, i, oop_notation_aka_disable_implicite_last);
806 }
807
808 if (tokenizer.IsOperator('(')) {
809 return ParseCall(left, nullptr, false);
810 }
811 return left;
812
813 }
814
815 6 PExpression ScriptParser::ParseAtom(void)
816 {
817
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 6 times.
6 if (tokenizer.IsIdentifier("function")) {
818 tokenizer.NextToken();
819 return ParseFunctionDefinition();
820 }
821
2/2
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 16 taken 3 times.
6 else if (tokenizer.IsIdentifier()) {
822 3 const char* name = tokenizer.AsIdentifier();
823 3 tokenizer.NextToken();
824
2/6
✓ Branch 12 → 13 taken 3 times.
✗ Branch 12 → 72 not taken.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 3 times.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
3 return new ExpVariableReference(name);
825 }
826
2/2
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 26 taken 1 time.
3 else if (tokenizer.IsInt()) {
827 2 int result = tokenizer.AsInt();
828 2 tokenizer.NextToken();
829
3/8
✓ Branch 21 → 22 taken 2 times.
✗ Branch 21 → 75 not taken.
✓ Branch 22 → 23 taken 2 times.
✗ Branch 22 → 75 not taken.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 2 times.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
2 return new ExpConstant(result);
830 }
831
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 36 taken 1 time.
1 else if (tokenizer.IsLong()) {
832 int64_t result = tokenizer.AsLong();
833 tokenizer.NextToken();
834 return new ExpConstant(result);
835 }
836
1/2
✓ Branch 37 → 38 taken 1 time.
✗ Branch 37 → 46 not taken.
1 else if (tokenizer.IsFloat()) {
837 1 double result = tokenizer.AsFloat();
838 1 tokenizer.NextToken();
839
3/8
✓ Branch 41 → 42 taken 1 time.
✗ Branch 41 → 81 not taken.
✓ Branch 42 → 43 taken 1 time.
✗ Branch 42 → 81 not taken.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 1 time.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
1 return new ExpConstant(result);
840 }
841 else if (tokenizer.IsString()) {
842 const char* result = tokenizer.AsString();
843 tokenizer.NextToken();
844 return new ExpConstant(result);
845 }
846 #ifdef ARRAYS_AT_TOKENIZER_LEVEL
847 else if (tokenizer.IsArray()) {
848 std::vector<AVSValue>* result = tokenizer.AsArray(); // PF tokenizer returns new array
849 tokenizer.NextToken();
850 return new ExpConstant(result);
851 }
852 #endif
853 else if (tokenizer.IsOperator('(')) {
854 tokenizer.NextToken();
855 PExpression result = ParseAssignmentWithRet();
856 Expect(')');
857 return result;
858 }
859 else if (tokenizer.IsOperator('[')) { // array
860 // with NEW_AVSVALUE this case is unreachable
861 env->ThrowError("Script error: array is not supported on this version of avisynth");
862 return 0;
863 }
864 else {
865 env->ThrowError("Script error: syntax error");
866 return 0;
867 }
868 }
869
870 6 int ScriptParser::GetTokenAsComparisonOperator()
871 {
872
1/2
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 5 not taken.
6 if (!tokenizer.IsOperator())
873 6 return 0;
874 int op = tokenizer.AsOperator();
875 if (op == "=="_i || op == "!="_i || op == "<>"_i || op == '<' || op == '>' || op == "<="_i || op == ">="_i)
876 return op;
877 else
878 return 0;
879 }
880
881