View Javadoc
1   package io.jawk.frontend;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * Jawk
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2006 - 2026 MetricsHub
8    * ჻჻჻჻჻჻
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23   */
24  
25  import java.io.IOException;
26  import java.io.LineNumberReader;
27  import java.io.PrintStream;
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.EnumSet;
31  import java.util.HashMap;
32  import java.util.HashSet;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.function.Supplier;
37  import io.jawk.NotImplementedError;
38  import io.jawk.backend.AVM;
39  import io.jawk.ext.ExtensionFunction;
40  import io.jawk.intermediate.Address;
41  import io.jawk.intermediate.AwkTuples;
42  import io.jawk.util.ScriptSource;
43  import io.jawk.frontend.ast.LexerException;
44  import io.jawk.frontend.ast.ParserException;
45  
46  /**
47   * Converts the AWK script into a syntax tree,
48   * which is useful the backend that either compiles or interprets the script.
49   * <p>
50   * It contains the internal state of the parser and the lexer.
51   *
52   * @author Danny Daglas
53   */
54  public class AwkParser {
55  
56  	/**
57  	 * Flags that describe special behaviours of AST nodes. These replace the
58  	 * previous marker interfaces such as {@code Breakable} and
59  	 * {@code NonStatementAst}.
60  	 */
61  	private enum AstFlag {
62  		BREAKABLE,
63  		NEXTABLE,
64  		CONTINUEABLE,
65  		RETURNABLE,
66  		NON_STATEMENT
67  	}
68  
69  	/** Lexer token values. */
70  	enum Token {
71  		EOF,
72  		NEWLINE,
73  		SEMICOLON,
74  		ID,
75  		FUNC_ID,
76  		INTEGER,
77  		DOUBLE,
78  		STRING,
79  
80  		EQUALS,
81  
82  		AND,
83  		OR,
84  
85  		EQ,
86  		GT,
87  		GE,
88  		LT,
89  		LE,
90  		NE,
91  		NOT,
92  		PIPE,
93  		QUESTION_MARK,
94  		COLON,
95  		APPEND,
96  
97  		PLUS,
98  		MINUS,
99  		MULT,
100 		DIVIDE,
101 		MOD,
102 		POW,
103 		COMMA,
104 		MATCHES,
105 		NOT_MATCHES,
106 		DOLLAR,
107 
108 		INC,
109 		DEC,
110 
111 		PLUS_EQ,
112 		MINUS_EQ,
113 		MULT_EQ,
114 		DIV_EQ,
115 		MOD_EQ,
116 		POW_EQ,
117 
118 		OPEN_PAREN,
119 		CLOSE_PAREN,
120 		OPEN_BRACE,
121 		CLOSE_BRACE,
122 		OPEN_BRACKET,
123 		CLOSE_BRACKET,
124 
125 		BUILTIN_FUNC_NAME,
126 
127 		EXTENSION,
128 
129 		KW_FUNCTION,
130 		KW_BEGIN,
131 		KW_END,
132 		KW_IN,
133 		KW_IF,
134 		KW_ELSE,
135 		KW_WHILE,
136 		KW_FOR,
137 		KW_DO,
138 		KW_RETURN,
139 		KW_EXIT,
140 		KW_NEXT,
141 		KW_CONTINUE,
142 		KW_DELETE,
143 		KW_BREAK,
144 		KW_PRINT,
145 		KW_PRINTF,
146 		KW_GETLINE
147 	}
148 
149 	/**
150 	 * Contains a mapping of Jawk keywords to their
151 	 * token values.
152 	 * They closely correspond to AWK keywords, but with
153 	 * a few added extensions.
154 	 * <p>
155 	 * Keys are the keywords themselves, and values are the
156 	 * token values (equivalent to yytok values in lex/yacc).
157 	 * <p>
158 	 * <strong>Note:</strong> whether built-in AWK function names
159 	 * and special AWK variable names are formally keywords or not,
160 	 * they are not stored in this map. They are separated
161 	 * into other maps.
162 	 */
163 	private static final Map<String, Token> KEYWORDS = new HashMap<String, Token>();
164 
165 	static {
166 		// special keywords
167 		KEYWORDS.put("function", Token.KW_FUNCTION);
168 		KEYWORDS.put("BEGIN", Token.KW_BEGIN);
169 		KEYWORDS.put("END", Token.KW_END);
170 		KEYWORDS.put("in", Token.KW_IN);
171 
172 		// statements
173 		KEYWORDS.put("if", Token.KW_IF);
174 		KEYWORDS.put("else", Token.KW_ELSE);
175 		KEYWORDS.put("while", Token.KW_WHILE);
176 		KEYWORDS.put("for", Token.KW_FOR);
177 		KEYWORDS.put("do", Token.KW_DO);
178 		KEYWORDS.put("return", Token.KW_RETURN);
179 		KEYWORDS.put("exit", Token.KW_EXIT);
180 		KEYWORDS.put("next", Token.KW_NEXT);
181 		KEYWORDS.put("continue", Token.KW_CONTINUE);
182 		KEYWORDS.put("delete", Token.KW_DELETE);
183 		KEYWORDS.put("break", Token.KW_BREAK);
184 
185 		// special-form functions
186 		KEYWORDS.put("print", Token.KW_PRINT);
187 		KEYWORDS.put("printf", Token.KW_PRINTF);
188 		KEYWORDS.put("getline", Token.KW_GETLINE);
189 	}
190 
191 	/**
192 	 * Built-in function token values.
193 	 * Built-in function token values are distinguished
194 	 * from lexer token values.
195 	 */
196 	private static int fIdx = 257;
197 	/**
198 	 * A mapping of built-in function names to their
199 	 * function token values.
200 	 * <p>
201 	 * <strong>Note:</strong> these are not lexer token
202 	 * values. Lexer token values are for keywords and
203 	 * operators.
204 	 */
205 	private static final Map<String, Integer> BUILTIN_FUNC_NAMES = new HashMap<String, Integer>();
206 
207 	static {
208 		BUILTIN_FUNC_NAMES.put("atan2", fIdx++);
209 		BUILTIN_FUNC_NAMES.put("close", fIdx++);
210 		BUILTIN_FUNC_NAMES.put("cos", fIdx++);
211 		BUILTIN_FUNC_NAMES.put("exp", fIdx++);
212 		BUILTIN_FUNC_NAMES.put("index", fIdx++);
213 		BUILTIN_FUNC_NAMES.put("int", fIdx++);
214 		BUILTIN_FUNC_NAMES.put("length", fIdx++);
215 		BUILTIN_FUNC_NAMES.put("log", fIdx++);
216 		BUILTIN_FUNC_NAMES.put("match", fIdx++);
217 		BUILTIN_FUNC_NAMES.put("rand", fIdx++);
218 		BUILTIN_FUNC_NAMES.put("sin", fIdx++);
219 		BUILTIN_FUNC_NAMES.put("split", fIdx++);
220 		BUILTIN_FUNC_NAMES.put("sprintf", fIdx++);
221 		BUILTIN_FUNC_NAMES.put("sqrt", fIdx++);
222 		BUILTIN_FUNC_NAMES.put("srand", fIdx++);
223 		BUILTIN_FUNC_NAMES.put("sub", fIdx++);
224 		BUILTIN_FUNC_NAMES.put("gsub", fIdx++);
225 		BUILTIN_FUNC_NAMES.put("substr", fIdx++);
226 		BUILTIN_FUNC_NAMES.put("system", fIdx++);
227 		BUILTIN_FUNC_NAMES.put("tolower", fIdx++);
228 		BUILTIN_FUNC_NAMES.put("toupper", fIdx++);
229 	}
230 
231 	private static final int SP_IDX = 257;
232 	/**
233 	 * Contains a mapping of Jawk special variables to their
234 	 * variable token values.
235 	 * As of this writing, they correspond exactly to
236 	 * standard AWK variables, no more, no less.
237 	 * <p>
238 	 * Keys are the variable names themselves, and values are the
239 	 * variable token values.
240 	 */
241 	private static final Map<String, Integer> SPECIAL_VAR_NAMES = new HashMap<String, Integer>();
242 
243 	static {
244 		SPECIAL_VAR_NAMES.put("NR", SP_IDX);
245 		SPECIAL_VAR_NAMES.put("FNR", SP_IDX);
246 		SPECIAL_VAR_NAMES.put("NF", SP_IDX);
247 		SPECIAL_VAR_NAMES.put("FS", SP_IDX);
248 		SPECIAL_VAR_NAMES.put("RS", SP_IDX);
249 		SPECIAL_VAR_NAMES.put("OFS", SP_IDX);
250 		SPECIAL_VAR_NAMES.put("ORS", SP_IDX);
251 		SPECIAL_VAR_NAMES.put("RSTART", SP_IDX);
252 		SPECIAL_VAR_NAMES.put("RLENGTH", SP_IDX);
253 		SPECIAL_VAR_NAMES.put("FILENAME", SP_IDX);
254 		SPECIAL_VAR_NAMES.put("SUBSEP", SP_IDX);
255 		SPECIAL_VAR_NAMES.put("CONVFMT", SP_IDX);
256 		SPECIAL_VAR_NAMES.put("OFMT", SP_IDX);
257 		SPECIAL_VAR_NAMES.put("ENVIRON", SP_IDX);
258 		SPECIAL_VAR_NAMES.put("ARGC", SP_IDX);
259 		SPECIAL_VAR_NAMES.put("ARGV", SP_IDX);
260 	}
261 
262 	/**
263 	 * Defined as concrete implementation class (not an
264 	 * interface reference) as to not clutter the interface
265 	 * with methods appropriate for private access, only.
266 	 */
267 	private final AwkSymbolTableImpl symbolTable = new AwkSymbolTableImpl();
268 
269 	private final Map<String, ExtensionFunction> extensions;
270 	private final boolean allowArraysOfArrays;
271 
272 	/**
273 	 * <p>
274 	 * Constructor for AwkParser.
275 	 * </p>
276 	 *
277 	 * @param extensions a {@link java.util.Map} object
278 	 */
279 	public AwkParser(Map<String, ExtensionFunction> extensions, boolean allowArraysOfArrays) {
280 		this.extensions = extensions == null ? Collections.emptyMap() : new HashMap<>(extensions);
281 		this.allowArraysOfArrays = allowArraysOfArrays;
282 	}
283 
284 	private List<ScriptSource> scriptSources;
285 	private int scriptSourcesCurrentIndex;
286 	private LineNumberReader reader;
287 	private int c;
288 	private Token token;
289 
290 	private StringBuffer text = new StringBuffer();
291 	private StringBuffer string = new StringBuffer();
292 	private StringBuffer regexp = new StringBuffer();
293 
294 	private void read() throws IOException {
295 		text.append((char) c);
296 		c = reader.read();
297 		// completely bypass \r's
298 		while (c == '\r') {
299 			c = reader.read();
300 		}
301 		if (c < 0 && (scriptSourcesCurrentIndex + 1) < scriptSources.size()) {
302 			scriptSourcesCurrentIndex++;
303 			reader = new LineNumberReader(scriptSources.get(scriptSourcesCurrentIndex).getReader());
304 			read();
305 		}
306 	}
307 
308 	/**
309 	 * Skip all whitespaces and comments
310 	 *
311 	 * @throws IOException
312 	 */
313 	private void skipWhitespaces() throws IOException {
314 		while (c == ' ' || c == '\t' || c == '#' || c == '\n') {
315 			if (c == '#') {
316 				while (c >= 0 && c != '\n') {
317 					read();
318 				}
319 			}
320 			read();
321 		}
322 	}
323 
324 	/**
325 	 * Parse the script streamed by script_reader. Build and return the
326 	 * root of the abstract syntax tree which represents the Jawk script.
327 	 *
328 	 * @param localScriptSources List of script sources
329 	 * @return The abstract syntax tree of this script.
330 	 * @throws java.io.IOException upon an IO error.
331 	 */
332 	public AstNode parse(List<ScriptSource> localScriptSources) throws IOException {
333 		if (localScriptSources == null || localScriptSources.isEmpty()) {
334 			throw new IOException("No script sources supplied");
335 		}
336 		this.scriptSources = Collections.unmodifiableList(new ArrayList<>(localScriptSources));
337 		scriptSourcesCurrentIndex = 0;
338 		reader = new LineNumberReader(this.scriptSources.get(scriptSourcesCurrentIndex).getReader());
339 		read();
340 		lexer();
341 		return SCRIPT();
342 	}
343 
344 	/**
345 	 * Parse a single AWK expression and return the corresponding AST.
346 	 *
347 	 * @param expressionSource The expression to parse (not a statement or rule, just an expression)
348 	 * @return tuples representing the expression
349 	 * @throws IOException upon an IO error or parsing error
350 	 */
351 	public AstNode parseExpression(ScriptSource expressionSource) throws IOException {
352 
353 		// Sanity check
354 		if (expressionSource == null) {
355 			throw new IOException("No source supplied");
356 		}
357 
358 		// Reader of the expression
359 		this.scriptSources = Collections.singletonList(expressionSource);
360 		scriptSourcesCurrentIndex = 0;
361 		reader = new LineNumberReader(this.scriptSources.get(scriptSourcesCurrentIndex).getReader());
362 
363 		// Initialize the lexer
364 		read();
365 		lexer();
366 
367 		// An expression is a TERNARY_EXPRESSION
368 		return EXPRESSION_TO_EVALUATE();
369 	}
370 
371 	private LexerException lexerException(String msg) {
372 		return new LexerException(
373 				msg,
374 				scriptSources.get(scriptSourcesCurrentIndex).getDescription(),
375 				reader.getLineNumber());
376 	}
377 
378 	/**
379 	 * Returns the current 1-based source line number to stamp onto AST nodes that
380 	 * will later emit tuple line markers for runtime error reporting.
381 	 *
382 	 * @return current source line number using 1-based counting
383 	 */
384 	private int currentSourceLineNumber() {
385 		return reader.getLineNumber() + 1;
386 	}
387 
388 	/**
389 	 * Reads the string and handle all escape codes.
390 	 *
391 	 * @throws IOException
392 	 */
393 	private void readString() throws IOException {
394 		string.setLength(0);
395 
396 		while (token != Token.EOF && c > 0 && c != '"' && c != '\n') {
397 			if (c == '\\') {
398 				read();
399 				switch (c) {
400 				case 'n':
401 					string.append('\n');
402 					break;
403 				case 't':
404 					string.append('\t');
405 					break;
406 				case 'r':
407 					string.append('\r');
408 					break;
409 				case 'a':
410 					string.append('\007');
411 					break; // BEL 0x07
412 				case 'b':
413 					string.append('\010');
414 					break; // BS 0x08
415 				case 'f':
416 					string.append('\014');
417 					break; // FF 0x0C
418 				case 'v':
419 					string.append('\013');
420 					break; // VT 0x0B
421 				// Octal notation: \N \NN \NNN
422 				case '0':
423 				case '1':
424 				case '2':
425 				case '3':
426 				case '4':
427 				case '5':
428 				case '6':
429 				case '7': {
430 					int octalChar = c - '0';
431 					read();
432 					if (c >= '0' && c <= '7') {
433 						octalChar = (octalChar << 3) + c - '0';
434 						read();
435 						if (c >= '0' && c <= '7') {
436 							octalChar = (octalChar << 3) + c - '0';
437 							read();
438 						}
439 					}
440 					string.append((char) octalChar);
441 					continue;
442 				}
443 				// Hexadecimal notation: \xN \xNN
444 				case 'x': {
445 					int hexChar = 0;
446 					read();
447 					if (c >= '0' && c <= '9') {
448 						hexChar = c - '0';
449 					} else if (c >= 'A' && c <= 'F') {
450 						hexChar = c - 'A' + 10;
451 					} else if (c >= 'a' && c <= 'f') {
452 						hexChar = c - 'a' + 10;
453 					} else {
454 						string.append('x');
455 						continue;
456 					}
457 					read();
458 					if (c >= '0' && c <= '9') {
459 						hexChar = (hexChar << 4) + c - '0';
460 					} else if (c >= 'A' && c <= 'F') {
461 						hexChar = (hexChar << 4) + c - 'A' + 10;
462 					} else if (c >= 'a' && c <= 'f') {
463 						hexChar = (hexChar << 4) + c - 'a' + 10;
464 					} else {
465 						// Append what we already have, and continue directly, because we already have read the next char
466 						string.append((char) hexChar);
467 						continue;
468 					}
469 					string.append((char) hexChar);
470 					break;
471 				}
472 				default:
473 					string.append((char) c);
474 					break; // Remove the backslash
475 				}
476 			} else {
477 				string.append((char) c);
478 			}
479 			read();
480 		}
481 		if (token == Token.EOF || c == '\n' || c <= 0) {
482 			throw lexerException("Unterminated string: " + text);
483 		}
484 		read();
485 	}
486 
487 	/**
488 	 * Reads the regular expression (between slashes '/') and handle '\/'.
489 	 *
490 	 * @throws IOException
491 	 */
492 	private void readRegexp() throws IOException {
493 		regexp.setLength(0);
494 
495 		while (token != Token.EOF && c > 0 && c != '/' && c != '\n') {
496 			if (c == '\\') {
497 				read();
498 				if (c != '/') {
499 					regexp.append('\\');
500 				}
501 			}
502 			regexp.append((char) c);
503 			read();
504 		}
505 		if (token == Token.EOF || c == '\n' || c <= 0) {
506 			throw lexerException("Unterminated string: " + text);
507 		}
508 		read();
509 	}
510 
511 	private Token lexer(Token expectedToken) throws IOException {
512 		if (token != expectedToken) {
513 			throw parserException(
514 					"Expecting " + expectedToken.name() + ". Found: " + token.name() + " (" + text + ")");
515 		}
516 		return lexer();
517 	}
518 
519 	private Token lexer() throws IOException {
520 		// clear whitespace
521 		while (c >= 0 && (c == ' ' || c == '\t' || c == '#' || c == '\\')) {
522 			if (c == '\\') {
523 				read();
524 				if (c == '\n') {
525 					read();
526 				}
527 				continue;
528 			}
529 			if (c == '#') {
530 				// kill comment
531 				while (c >= 0 && c != '\n') {
532 					read();
533 				}
534 			} else {
535 				read();
536 			}
537 		}
538 		text.setLength(0);
539 		if (c < 0) {
540 			token = Token.EOF;
541 			return token;
542 		}
543 		if (c == ',') {
544 			read();
545 			skipWhitespaces();
546 			token = Token.COMMA;
547 			return token;
548 		}
549 		if (c == '(') {
550 			read();
551 			token = Token.OPEN_PAREN;
552 			return token;
553 		}
554 		if (c == ')') {
555 			read();
556 			token = Token.CLOSE_PAREN;
557 			return token;
558 		}
559 		if (c == '{') {
560 			read();
561 			skipWhitespaces();
562 			token = Token.OPEN_BRACE;
563 			return token;
564 		}
565 		if (c == '}') {
566 			read();
567 			token = Token.CLOSE_BRACE;
568 			return token;
569 		}
570 		if (c == '[') {
571 			read();
572 			token = Token.OPEN_BRACKET;
573 			return token;
574 		}
575 		if (c == ']') {
576 			read();
577 			token = Token.CLOSE_BRACKET;
578 			return token;
579 		}
580 		if (c == '$') {
581 			read();
582 			token = Token.DOLLAR;
583 			return token;
584 		}
585 		if (c == '~') {
586 			read();
587 			token = Token.MATCHES;
588 			return token;
589 		}
590 		if (c == '?') {
591 			read();
592 			skipWhitespaces();
593 			token = Token.QUESTION_MARK;
594 			return token;
595 		}
596 		if (c == ':') {
597 			read();
598 			skipWhitespaces();
599 			token = Token.COLON;
600 			return token;
601 		}
602 		if (c == '&') {
603 			read();
604 			if (c == '&') {
605 				read();
606 				skipWhitespaces();
607 				token = Token.AND;
608 				return token;
609 			}
610 			throw lexerException("use && for logical and");
611 		}
612 		if (c == '|') {
613 			read();
614 			if (c == '|') {
615 				read();
616 				skipWhitespaces();
617 				token = Token.OR;
618 				return token;
619 			}
620 			token = Token.PIPE;
621 			return token;
622 		}
623 		if (c == '=') {
624 			read();
625 			if (c == '=') {
626 				read();
627 				token = Token.EQ;
628 				return token;
629 			}
630 			token = Token.EQUALS;
631 			return token;
632 		}
633 		if (c == '+') {
634 			read();
635 			if (c == '=') {
636 				read();
637 				token = Token.PLUS_EQ;
638 				return token;
639 			} else if (c == '+') {
640 				read();
641 				token = Token.INC;
642 				return token;
643 			}
644 			token = Token.PLUS;
645 			return token;
646 		}
647 		if (c == '-') {
648 			read();
649 			if (c == '=') {
650 				read();
651 				token = Token.MINUS_EQ;
652 				return token;
653 			} else if (c == '-') {
654 				read();
655 				token = Token.DEC;
656 				return token;
657 			}
658 			token = Token.MINUS;
659 			return token;
660 		}
661 		if (c == '*') {
662 			read();
663 			if (c == '=') {
664 				read();
665 				token = Token.MULT_EQ;
666 				return token;
667 			} else if (c == '*') {
668 				read();
669 				if (c == '=') {
670 					read();
671 					token = Token.POW_EQ;
672 					return token;
673 				}
674 				token = Token.POW;
675 				return token;
676 			}
677 			token = Token.MULT;
678 			return token;
679 		}
680 		if (c == '/') {
681 			read();
682 			if (c == '=') {
683 				read();
684 				token = Token.DIV_EQ;
685 				return token;
686 			}
687 			token = Token.DIVIDE;
688 			return token;
689 		}
690 		if (c == '%') {
691 			read();
692 			if (c == '=') {
693 				read();
694 				token = Token.MOD_EQ;
695 				return token;
696 			}
697 			token = Token.MOD;
698 			return token;
699 		}
700 		if (c == '^') {
701 			read();
702 			if (c == '=') {
703 				read();
704 				token = Token.POW_EQ;
705 				return token;
706 			}
707 			token = Token.POW;
708 			return token;
709 		}
710 		if (c == '>') {
711 			read();
712 			if (c == '=') {
713 				read();
714 				token = Token.GE;
715 				return token;
716 			} else if (c == '>') {
717 				read();
718 				token = Token.APPEND;
719 				return token;
720 			}
721 			token = Token.GT;
722 			return token;
723 		}
724 		if (c == '<') {
725 			read();
726 			if (c == '=') {
727 				read();
728 				token = Token.LE;
729 				return token;
730 			}
731 			token = Token.LT;
732 			return token;
733 		}
734 		if (c == '!') {
735 			read();
736 			if (c == '=') {
737 				read();
738 				token = Token.NE;
739 				return token;
740 			} else if (c == '~') {
741 				read();
742 				token = Token.NOT_MATCHES;
743 				return token;
744 			}
745 			token = Token.NOT;
746 			return token;
747 		}
748 
749 		if (c == '.') {
750 			// double!
751 			read();
752 			boolean hit = false;
753 			while (c > 0 && Character.isDigit(c)) {
754 				hit = true;
755 				read();
756 			}
757 			if (!hit) {
758 				throw lexerException("Decimal point encountered with no values on either side.");
759 			}
760 			token = Token.DOUBLE;
761 			return token;
762 		}
763 
764 		if (Character.isDigit(c)) {
765 			// integer or double.
766 			read();
767 			while (c > 0) {
768 				if (c == '.') {
769 					// double!
770 					read();
771 					while (c > 0 && Character.isDigit(c)) {
772 						read();
773 					}
774 					token = Token.DOUBLE;
775 					return token;
776 				} else if (Character.isDigit(c)) {
777 					// integer or double.
778 					read();
779 				} else {
780 					break;
781 				}
782 			}
783 			// integer, only
784 			token = Token.INTEGER;
785 			return token;
786 		}
787 
788 		if (Character.isJavaIdentifierStart(c)) {
789 			read();
790 			while (Character.isJavaIdentifierPart(c)) {
791 				read();
792 			}
793 			// check for certain keywords
794 			// extensions override built-in stuff
795 			if (extensions.get(text.toString()) != null) {
796 				token = Token.EXTENSION;
797 				return token;
798 			}
799 			Token kwToken = KEYWORDS.get(text.toString());
800 			if (kwToken != null) {
801 				token = kwToken;
802 				return token;
803 			}
804 			Integer builtinIdx = BUILTIN_FUNC_NAMES.get(text.toString());
805 			if (builtinIdx != null) {
806 				token = Token.BUILTIN_FUNC_NAME;
807 				return token;
808 			}
809 			if (c == '(') {
810 				token = Token.FUNC_ID;
811 				return token;
812 			} else {
813 				token = Token.ID;
814 				return token;
815 			}
816 		}
817 
818 		if (c == ';') {
819 			read();
820 			while (c == ' ' || c == '\t' || c == '\n' || c == '#') {
821 				if (c == '\n') {
822 					break;
823 				}
824 				if (c == '#') {
825 					while (c >= 0 && c != '\n') {
826 						read();
827 					}
828 					if (c == '\n') {
829 						read();
830 					}
831 				} else {
832 					read();
833 				}
834 			}
835 			token = Token.SEMICOLON;
836 			return token;
837 		}
838 
839 		if (c == '\n') {
840 			read();
841 			while (c == ' ' || c == '\t' || c == '#' || c == '\n') {
842 				if (c == '#') {
843 					while (c >= 0 && c != '\n') {
844 						read();
845 					}
846 				}
847 				read();
848 			}
849 			token = Token.NEWLINE;
850 			return token;
851 		}
852 
853 		if (c == '"') {
854 			// string
855 			read();
856 			readString();
857 			token = Token.STRING;
858 			return token;
859 		}
860 
861 		/*
862 		 * if (c == '\\') {
863 		 * c = reader.read();
864 		 * // completely bypass \r's
865 		 * while(c == '\r') c = reader.read();
866 		 * if (c<0)
867 		 * chr=0; // eof
868 		 * else
869 		 * chr=c;
870 		 * }
871 		 */
872 
873 		throw lexerException("Invalid character (" + c + "): " + ((char) c));
874 	}
875 
876 	// SUPPORTING FUNCTIONS/METHODS
877 	private void terminator() throws IOException {
878 		// like optTerminator, except error if no terminator was found
879 		if (!optTerminator()) {
880 			throw parserException("Expecting statement terminator. Got " + token.name() + ": " + text);
881 		}
882 	}
883 
884 	private boolean optTerminator() throws IOException {
885 		if (optNewline()) {
886 			return true;
887 		} else if (token == Token.EOF || token == Token.CLOSE_BRACE) {
888 			return true; // do nothing
889 		} else if (token == Token.SEMICOLON) {
890 			lexer();
891 			return true;
892 		} else {
893 			// no terminator consumed
894 			return false;
895 		}
896 	}
897 
898 	private boolean optNewline() throws IOException {
899 		if (token == Token.NEWLINE) {
900 			lexer();
901 			return true;
902 		} else {
903 			return false;
904 		}
905 	}
906 
907 	// RECURSIVE DECENT PARSER:
908 	// CHECKSTYLE.OFF: MethodName
909 	// SCRIPT : \n [RULE_LIST] Token.EOF
910 	AST SCRIPT() throws IOException {
911 		AST rl;
912 		if (token != Token.EOF) {
913 			rl = RULE_LIST();
914 		} else {
915 			rl = null;
916 		}
917 		lexer(Token.EOF);
918 		return rl;
919 	}
920 
921 	// EXPRESSION_TO_EVALUATE: [TERNARY_EXPRESSION] Token.EOF
922 	// Used to parse simple expressions to evaluate instead of full scripts
923 	AST EXPRESSION_TO_EVALUATE() throws IOException {
924 		AST exprAst = token != Token.EOF ? TERNARY_EXPRESSION(null, true, false, true) : null;
925 		lexer(Token.EOF);
926 		return new ExpressionToEvaluateAst(exprAst);
927 	}
928 
929 	// RULE_LIST : \n [ ( RULE | FUNCTION terminator ) optTerminator RULE_LIST ]
930 	AST RULE_LIST() throws IOException {
931 		optNewline();
932 		AST ruleOrFunction = null;
933 		if (token == Token.KW_FUNCTION) {
934 			ruleOrFunction = FUNCTION();
935 		} else if (token != Token.EOF) {
936 			ruleOrFunction = RULE();
937 		} else {
938 			return null;
939 		}
940 		optTerminator(); // newline or ; (maybe)
941 		return new RuleListAst(ruleOrFunction, RULE_LIST());
942 	}
943 
944 	// FUNCTION: function functionName( [FORMAL_PARAM_LIST] ) STATEMENT_LIST
945 	AST FUNCTION() throws IOException {
946 		expectKeyword("function");
947 		String functionName;
948 		if (token == Token.FUNC_ID || token == Token.ID) {
949 			functionName = text.toString();
950 			lexer();
951 		} else {
952 			throw parserException("Expecting function name. Got " + token.name() + ": " + text);
953 		}
954 		symbolTable.setFunctionName(functionName);
955 		lexer(Token.OPEN_PAREN);
956 		AST formalParamList;
957 		if (token == Token.CLOSE_PAREN) {
958 			formalParamList = null;
959 		} else {
960 			formalParamList = FORMAL_PARAM_LIST(functionName);
961 		}
962 		lexer(Token.CLOSE_PAREN);
963 		optNewline();
964 
965 		lexer(Token.OPEN_BRACE);
966 		AST functionBlock = STATEMENT_LIST();
967 		lexer(Token.CLOSE_BRACE);
968 		symbolTable.clearFunctionName(functionName);
969 		return symbolTable.addFunctionDef(functionName, formalParamList, functionBlock);
970 	}
971 
972 	// FORMAT_PARAM_LIST:
973 	AST FORMAL_PARAM_LIST(String functionName) throws IOException {
974 		if (token == Token.ID) {
975 			String id = text.toString();
976 			symbolTable.addFunctionParameter(functionName, id);
977 			lexer();
978 			if (token == Token.COMMA) {
979 				lexer();
980 				optNewline();
981 				AST rest = FORMAL_PARAM_LIST(functionName);
982 				if (rest == null) {
983 					throw parserException("Cannot terminate a formal parameter list with a comma.");
984 				} else {
985 					return new FunctionDefParamListAst(id, rest);
986 				}
987 			} else {
988 				return new FunctionDefParamListAst(id, null);
989 			}
990 		} else {
991 			return null;
992 		}
993 	}
994 
995 	// RULE : [ASSIGNMENT_EXPRESSION] [ { STATEMENT_LIST } ]
996 	AST RULE() throws IOException {
997 		AST optExpr;
998 		AST optStmts;
999 		if (token == Token.KW_BEGIN) {
1000 			lexer();
1001 			optExpr = symbolTable.addBEGIN();
1002 		} else if (token == Token.KW_END) {
1003 			lexer();
1004 			optExpr = symbolTable.addEND();
1005 		} else if (token != Token.OPEN_BRACE && token != Token.SEMICOLON && token != Token.NEWLINE && token != Token.EOF) {
1006 			// true = allow comparators, allow IN keyword, do Token.NOT allow multidim indices expressions
1007 			optExpr = ASSIGNMENT_EXPRESSION(null, true, true, false);
1008 			// for ranges, like conditionStart, conditionEnd
1009 			if (token == Token.COMMA) {
1010 				lexer();
1011 				optNewline();
1012 				// true = allow comparators, allow IN keyword, do Token.NOT allow multidim indices expressions
1013 				optExpr = new ConditionPairAst(
1014 						optExpr,
1015 						ASSIGNMENT_EXPRESSION(null, true, true, false));
1016 			}
1017 		} else {
1018 			optExpr = null;
1019 		}
1020 		if (token == Token.OPEN_BRACE) {
1021 			lexer();
1022 			optStmts = STATEMENT_LIST();
1023 			lexer(Token.CLOSE_BRACE);
1024 		} else {
1025 			optStmts = null;
1026 		}
1027 		return new RuleAst(optExpr, optStmts);
1028 	}
1029 
1030 	// STATEMENT_LIST : [ STATEMENT_BLOCK|STATEMENT STATEMENT_LIST ]
1031 	private AST STATEMENT_LIST() throws IOException {
1032 		// statement lists can only live within curly brackets (braces)
1033 		optNewline();
1034 		if (token == Token.CLOSE_BRACE || token == Token.EOF) {
1035 			return null;
1036 		}
1037 		AST stmt;
1038 		if (token == Token.OPEN_BRACE) {
1039 			lexer();
1040 			stmt = STATEMENT_LIST();
1041 			lexer(Token.CLOSE_BRACE);
1042 		} else {
1043 			if (token == Token.SEMICOLON) {
1044 				// an empty statement (;)
1045 				// do not polute the syntax tree with nulls in this case
1046 				// just return the next statement (recursively)
1047 				lexer();
1048 				return STATEMENT_LIST();
1049 			} else {
1050 				stmt = STATEMENT();
1051 			}
1052 		}
1053 
1054 		AST rest = STATEMENT_LIST();
1055 		if (rest == null) {
1056 			return stmt;
1057 		} else if (stmt == null) {
1058 			return rest;
1059 		} else {
1060 			return new StatementListAst(stmt, rest);
1061 		}
1062 	}
1063 
1064 	/**
1065 	 * Parse a (possibly comma-separated) list of ASSIGNMENT_EXPRESSIONs.
1066 	 *
1067 	 * @param allowComparisons
1068 	 *        – true ⇒ treat ‘>’ and ‘<’ as comparison operators
1069 	 *        – false ⇒ treat ‘>’ and ‘<’ as redirection tokens (break out)
1070 	 * @param allowInKeyword
1071 	 *        – true ⇒ allow the “in” keyword inside expressions
1072 	 *        – false ⇒ disallow “in”
1073 	 */
1074 	AST EXPRESSION_LIST(boolean allowComparisons, boolean allowInKeyword) throws IOException {
1075 		// 1) Parse exactly one assignment expression.
1076 		// Passing `allowComparisons` will decide if ‘>’/’<’ become comparisons or redirectors.
1077 		AST expr = ASSIGNMENT_EXPRESSION(null, allowComparisons, allowInKeyword, /* allowMultidim= */ false);
1078 
1079 		// 2) If the next token is a comma, consume it and build the rest of the list.
1080 		// This supports both regular function calls and print/printf argument lists.
1081 		if (token == Token.COMMA) {
1082 			lexer(); // consume ','
1083 			optNewline(); // allow newline after comma (AWK style)
1084 
1085 			AST rest = EXPRESSION_LIST(allowComparisons, allowInKeyword);
1086 			return new FunctionCallParamListAst(expr, rest);
1087 		}
1088 
1089 		// 3) No comma ⇒ this single expression is a one‐element list.
1090 		return new FunctionCallParamListAst(expr, null);
1091 	}
1092 
1093 	private AST ASSIGNMENT_EXPRESSION(
1094 			AST left,
1095 			boolean allowComparison,
1096 			boolean allowInKeyword,
1097 			boolean allowMultidimIndices)
1098 			throws IOException {
1099 		AST commaExpression = COMMA_EXPRESSION(left, allowComparison, allowInKeyword, allowMultidimIndices);
1100 		if (token == Token.EQUALS
1101 				|| token == Token.PLUS_EQ
1102 				|| token == Token.MINUS_EQ
1103 				|| token == Token.MULT_EQ
1104 				|| token == Token.DIV_EQ
1105 				|| token == Token.MOD_EQ
1106 				|| token == Token.POW_EQ) {
1107 			Token op = token;
1108 			String txt = text.toString();
1109 			lexer();
1110 			AST assignmentExpression = ASSIGNMENT_EXPRESSION(
1111 					null,
1112 					allowComparison,
1113 					allowInKeyword,
1114 					allowMultidimIndices);
1115 			return new AssignmentExpressionAst(commaExpression, op, txt, assignmentExpression);
1116 		}
1117 		return commaExpression;
1118 	}
1119 
1120 	// COMMA_EXPRESSION = TERNARY_EXPRESSION [, COMMA_EXPRESSION] !!!ONLY IF!!! allowMultidimIndices is true
1121 	// allowMultidimIndices is set to true when we need (1,2,3,4) expressions to collapse into an array index expression
1122 	// (converts 1,2,3,4 to 1 SUBSEP 2 SUBSEP 3 SUBSEP 4) after an open parenthesis (grouping) expression starter
1123 	private AST COMMA_EXPRESSION(
1124 			AST left,
1125 			boolean allowComparison,
1126 			boolean allowInKeyword,
1127 			boolean allowMultidimIndices)
1128 			throws IOException {
1129 		AST concatExpression = TERNARY_EXPRESSION(left, allowComparison, allowInKeyword, allowMultidimIndices);
1130 		if (allowMultidimIndices && token == Token.COMMA) {
1131 			lexer();
1132 			optNewline();
1133 			AST rest = COMMA_EXPRESSION(null, allowComparison, allowInKeyword, allowMultidimIndices);
1134 			if (rest instanceof ArrayIndexAst) {
1135 				return new ArrayIndexAst(concatExpression, rest);
1136 			}
1137 			return new ArrayIndexAst(concatExpression, new ArrayIndexAst(rest, null));
1138 		}
1139 		return concatExpression;
1140 	}
1141 
1142 	// TERNARY_EXPRESSION = LOGICAL_OR_EXPRESSION [ ? TERNARY_EXPRESSION : TERNARY_EXPRESSION ]
1143 	private AST TERNARY_EXPRESSION(
1144 			AST left,
1145 			boolean allowComparison,
1146 			boolean allowInKeyword,
1147 			boolean allowMultidimIndices)
1148 			throws IOException {
1149 		AST condition = LOGICAL_OR_EXPRESSION(left, allowComparison, allowInKeyword, allowMultidimIndices);
1150 		if (token == Token.QUESTION_MARK) {
1151 			lexer();
1152 			AST trueBlock = TERNARY_EXPRESSION(null, allowComparison, allowInKeyword, allowMultidimIndices);
1153 			lexer(Token.COLON);
1154 			AST falseBlock = TERNARY_EXPRESSION(null, allowComparison, allowInKeyword, allowMultidimIndices);
1155 			return new TernaryExpressionAst(condition, trueBlock, falseBlock);
1156 		}
1157 		return condition;
1158 	}
1159 
1160 	// LOGICAL_OR_EXPRESSION = LOGICAL_AND_EXPRESSION [ || LOGICAL_OR_EXPRESSION ]
1161 	private AST LOGICAL_OR_EXPRESSION(
1162 			AST left,
1163 			boolean allowComparison,
1164 			boolean allowInKeyword,
1165 			boolean allowMultidimIndices)
1166 			throws IOException {
1167 		AST result = LOGICAL_AND_EXPRESSION(left, allowComparison, allowInKeyword, allowMultidimIndices);
1168 		while (token == Token.OR) {
1169 			Token op = token;
1170 			String txt = text.toString();
1171 			lexer();
1172 			AST rhs = LOGICAL_OR_EXPRESSION(null, allowComparison, allowInKeyword, allowMultidimIndices);
1173 			result = new LogicalExpressionAst(result, op, txt, rhs);
1174 		}
1175 		return result;
1176 	}
1177 
1178 	// LOGICAL_AND_EXPRESSION = IN_EXPRESSION [ && LOGICAL_AND_EXPRESSION ]
1179 	private AST LOGICAL_AND_EXPRESSION(
1180 			AST left,
1181 			boolean allowComparison,
1182 			boolean allowInKeyword,
1183 			boolean allowMultidimIndices)
1184 			throws IOException {
1185 		AST result = IN_EXPRESSION(left, allowComparison, allowInKeyword, allowMultidimIndices);
1186 		while (token == Token.AND) {
1187 			Token op = token;
1188 			String txt = text.toString();
1189 			lexer();
1190 			AST rhs = LOGICAL_AND_EXPRESSION(null, allowComparison, allowInKeyword, allowMultidimIndices);
1191 			result = new LogicalExpressionAst(result, op, txt, rhs);
1192 		}
1193 		return result;
1194 	}
1195 
1196 	// IN_EXPRESSION = MATCHING_EXPRESSION [ IN_EXPRESSION ]
1197 	// allowInKeyword is set false while parsing the first expression within
1198 	// a for() statement (because it could be "for (key in arr)", and this
1199 	// production will consume and the for statement will never have a chance
1200 	// of processing it
1201 	// all other times, it is true
1202 	private AST IN_EXPRESSION(
1203 			AST left,
1204 			boolean allowComparison,
1205 			boolean allowInKeyword,
1206 			boolean allowMultidimIndices)
1207 			throws IOException {
1208 		AST result = MATCHING_EXPRESSION(left, allowComparison, allowInKeyword, allowMultidimIndices);
1209 		if (allowInKeyword && token == Token.KW_IN) {
1210 			lexer();
1211 			result = new InExpressionAst(
1212 					result,
1213 					IN_EXPRESSION(null, allowComparison, allowInKeyword, allowMultidimIndices));
1214 		}
1215 		return result;
1216 	}
1217 
1218 	// MATCHING_EXPRESSION = COMPARISON_EXPRESSION [ (~,!~) MATCHING_EXPRESSION ]
1219 	private AST MATCHING_EXPRESSION(
1220 			AST left,
1221 			boolean allowComparison,
1222 			boolean allowInKeyword,
1223 			boolean allowMultidimIndices)
1224 			throws IOException {
1225 		AST result = COMPARISON_EXPRESSION(left, allowComparison, allowInKeyword, allowMultidimIndices);
1226 		while (token == Token.MATCHES || token == Token.NOT_MATCHES) {
1227 			Token op = token;
1228 			String txt = text.toString();
1229 			lexer();
1230 			AST rhs = MATCHING_EXPRESSION(null, allowComparison, allowInKeyword, allowMultidimIndices);
1231 			result = new ComparisonExpressionAst(result, op, txt, rhs);
1232 		}
1233 		return result;
1234 	}
1235 
1236 	// COMPARISON_EXPRESSION = CONCAT_EXPRESSION [ (==,>,>=,<,<=,!=,|) COMPARISON_EXPRESSION ]
1237 	// allowComparison is set false when within a print/printf statement;
1238 	// all other times it is set true
1239 	private AST COMPARISON_EXPRESSION(
1240 			AST left,
1241 			boolean allowComparison,
1242 			boolean allowInKeyword,
1243 			boolean allowMultidimIndices)
1244 			throws IOException {
1245 		AST result = CONCAT_EXPRESSION(left, allowComparison, allowInKeyword, allowMultidimIndices);
1246 		if (token == Token.EQ
1247 				|| token == Token.GE
1248 				|| token == Token.LT
1249 				|| token == Token.LE
1250 				|| token == Token.NE
1251 				|| (token == Token.GT && allowComparison)) {
1252 			Token op = token;
1253 			String txt = text.toString();
1254 			lexer();
1255 			AST rhs = COMPARISON_EXPRESSION(null, allowComparison, allowInKeyword, allowMultidimIndices);
1256 			return new ComparisonExpressionAst(result, op, txt, rhs);
1257 		} else if (allowComparison && token == Token.PIPE) {
1258 			lexer();
1259 			return GETLINE_EXPRESSION(result, allowComparison, allowInKeyword);
1260 		}
1261 
1262 		return result;
1263 	}
1264 
1265 	// CONCAT_EXPRESSION = EXPRESSION [ CONCAT_EXPRESSION ]
1266 	private AST CONCAT_EXPRESSION(
1267 			AST left,
1268 			boolean allowComparison,
1269 			boolean allowInKeyword,
1270 			boolean allowMultidimIndices)
1271 			throws IOException {
1272 		AST result = EXPRESSION(left, allowComparison, allowInKeyword, allowMultidimIndices);
1273 		if (token == Token.INTEGER
1274 				|| token == Token.DOUBLE
1275 				|| token == Token.OPEN_PAREN
1276 				|| token == Token.FUNC_ID
1277 				|| token == Token.INC
1278 				|| token == Token.DEC
1279 				|| token == Token.ID
1280 				|| token == Token.STRING
1281 				|| token == Token.DOLLAR
1282 				|| token == Token.BUILTIN_FUNC_NAME
1283 				|| token == Token.EXTENSION) {
1284 			return new ConcatExpressionAst(
1285 					result,
1286 					CONCAT_EXPRESSION(null, allowComparison, allowInKeyword, allowMultidimIndices));
1287 		}
1288 		return result;
1289 	}
1290 
1291 	// EXPRESSION : TERM [ (+|-) EXPRESSION ]
1292 	private AST EXPRESSION(
1293 			AST left,
1294 			boolean allowComparison,
1295 			boolean allowInKeyword,
1296 			boolean allowMultidimIndices)
1297 			throws IOException {
1298 		AST result = TERM(left, allowComparison, allowInKeyword, allowMultidimIndices);
1299 		while (token == Token.PLUS || token == Token.MINUS) {
1300 			Token op = token;
1301 			String txt = text.toString();
1302 			lexer();
1303 			AST nextTerm = TERM(null, allowComparison, allowInKeyword, allowMultidimIndices);
1304 			result = new BinaryExpressionAst(result, op, txt, nextTerm);
1305 		}
1306 		return result;
1307 	}
1308 
1309 	// TERM : UNARY_FACTOR [ (*|/|%) TERM ]
1310 	private AST TERM(
1311 			AST left,
1312 			boolean allowComparison,
1313 			boolean allowInKeyword,
1314 			boolean allowMultidimIndices)
1315 			throws IOException {
1316 		AST result = (left == null) ? UNARY_FACTOR(allowComparison, allowInKeyword, allowMultidimIndices) : left;
1317 		while (token == Token.MULT || token == Token.DIVIDE || token == Token.MOD) {
1318 			Token op = token;
1319 			String txt = text.toString();
1320 			lexer();
1321 			AST nextUnaryFactor = UNARY_FACTOR(allowComparison, allowInKeyword, allowMultidimIndices);
1322 			result = new BinaryExpressionAst(result, op, txt, nextUnaryFactor);
1323 		}
1324 		return result;
1325 	}
1326 
1327 	// UNARY_FACTOR : [ ! | - | + ] POWER_FACTOR
1328 	AST UNARY_FACTOR(boolean allowComparison, boolean allowInKeyword, boolean allowMultidimIndices)
1329 			throws IOException {
1330 		if (token == Token.NOT) {
1331 			lexer();
1332 			return new NotExpressionAst(POWER_FACTOR(null, allowComparison, allowInKeyword, allowMultidimIndices));
1333 		} else if (token == Token.MINUS) {
1334 			lexer();
1335 			return new NegativeExpressionAst(
1336 					POWER_FACTOR(null, allowComparison, allowInKeyword, allowMultidimIndices));
1337 		} else if (token == Token.PLUS) {
1338 			lexer();
1339 			return new UnaryPlusExpressionAst(
1340 					POWER_FACTOR(null, allowComparison, allowInKeyword, allowMultidimIndices));
1341 		} else {
1342 			return POWER_FACTOR(null, allowComparison, allowInKeyword, allowMultidimIndices);
1343 		}
1344 	}
1345 
1346 	// POWER_FACTOR : FACTOR_FOR_INCDEC [ ^ POWER_FACTOR ]
1347 	private AST POWER_FACTOR(
1348 			AST left,
1349 			boolean allowComparison,
1350 			boolean allowInKeyword,
1351 			boolean allowMultidimIndices)
1352 			throws IOException {
1353 		AST result = (left == null) ? FACTOR_FOR_INCDEC(allowComparison, allowInKeyword, allowMultidimIndices) : left;
1354 		if (token == Token.POW) {
1355 			Token op = token;
1356 			String txt = text.toString();
1357 			lexer();
1358 			AST rhs = POWER_FACTOR(null, allowComparison, allowInKeyword, allowMultidimIndices);
1359 			return new BinaryExpressionAst(result, op, txt, rhs);
1360 		}
1361 		return result;
1362 	}
1363 
1364 	// according to the spec, pre/post inc can occur
1365 	// only on lvalues, which are NAMES (IDs), array,
1366 	// or field references
1367 	private boolean isLvalue(AST ast) {
1368 		return (ast instanceof IDAst) || (ast instanceof ArrayReferenceAst) || (ast instanceof DollarExpressionAst);
1369 	}
1370 
1371 	AST FACTOR_FOR_INCDEC(boolean allowComparison, boolean allowInKeyword, boolean allowMultidimIndices)
1372 			throws IOException {
1373 		boolean preInc = false;
1374 		boolean preDec = false;
1375 		boolean postInc = false;
1376 		boolean postDec = false;
1377 		if (token == Token.INC) {
1378 			preInc = true;
1379 			lexer();
1380 		} else if (token == Token.DEC) {
1381 			preDec = true;
1382 			lexer();
1383 		}
1384 
1385 		AST factorAst = FACTOR(allowComparison, allowInKeyword, allowMultidimIndices);
1386 
1387 		if ((preInc || preDec) && !isLvalue(factorAst)) {
1388 			throw parserException("Cannot pre inc/dec a non-lvalue");
1389 		}
1390 
1391 		// only do post ops if:
1392 		// - factorAst is an lvalue
1393 		// - pre ops were not encountered
1394 		if (isLvalue(factorAst) && !preInc && !preDec) {
1395 			if (token == Token.INC) {
1396 				postInc = true;
1397 				lexer();
1398 			} else if (token == Token.DEC) {
1399 				postDec = true;
1400 				lexer();
1401 			}
1402 		}
1403 
1404 		if ((preInc || preDec) && (postInc || postDec)) {
1405 			throw parserException("Cannot do pre inc/dec Token.AND post inc/dec.");
1406 		}
1407 
1408 		if (preInc) {
1409 			return new PreIncAst(factorAst);
1410 		} else if (preDec) {
1411 			return new PreDecAst(factorAst);
1412 		} else if (postInc) {
1413 			return new PostIncAst(factorAst);
1414 		} else if (postDec) {
1415 			return new PostDecAst(factorAst);
1416 		} else {
1417 			return factorAst;
1418 		}
1419 	}
1420 
1421 	// FACTOR : '(' ASSIGNMENT_EXPRESSION ')' | Token.INTEGER | Token.DOUBLE | Token.STRING | GETLINE
1422 	// [Token.ID-or-array-or-$val] | /[=].../
1423 	// | [++|--] SYMBOL [++|--]
1424 	// AST FACTOR(boolean allowComparison, boolean allowInKeyword, boolean allow_post_incdec_operators)
1425 	AST FACTOR(boolean allowComparison, boolean allowInKeyword, boolean allowMultidimIndices) throws IOException {
1426 		if (token == Token.OPEN_PAREN) {
1427 			lexer();
1428 			// true = allow multi-dimensional array indices (i.e., commas for 1,2,3,4)
1429 			AST assignmentExpression = ASSIGNMENT_EXPRESSION(null, true, allowInKeyword, true);
1430 			if (allowMultidimIndices && (assignmentExpression instanceof ArrayIndexAst)) {
1431 				throw parserException("Cannot nest multi-dimensional array index expressions.");
1432 			}
1433 			lexer(Token.CLOSE_PAREN);
1434 			return assignmentExpression;
1435 		} else if (token == Token.INTEGER) {
1436 			AST integer = symbolTable.addINTEGER(text.toString());
1437 			lexer();
1438 			return integer;
1439 		} else if (token == Token.DOUBLE) {
1440 			AST dbl = symbolTable.addDOUBLE(text.toString());
1441 			lexer();
1442 			return dbl;
1443 		} else if (token == Token.STRING) {
1444 			AST str = symbolTable.addSTRING(string.toString());
1445 			lexer();
1446 			return str;
1447 		} else if (token == Token.KW_GETLINE) {
1448 			return GETLINE_EXPRESSION(null, allowComparison, allowInKeyword);
1449 		} else if (token == Token.DIVIDE || token == Token.DIV_EQ) {
1450 			readRegexp();
1451 			if (token == Token.DIV_EQ) {
1452 				regexp.insert(0, '=');
1453 			}
1454 			AST regexpAst = symbolTable.addREGEXP(regexp.toString());
1455 			lexer();
1456 			return regexpAst;
1457 		} else {
1458 			if (token == Token.DOLLAR) {
1459 				lexer();
1460 				if (token == Token.INC || token == Token.DEC) {
1461 					return new DollarExpressionAst(
1462 							FACTOR_FOR_INCDEC(allowComparison, allowInKeyword, allowMultidimIndices));
1463 				}
1464 				if (token == Token.NOT || token == Token.MINUS || token == Token.PLUS) {
1465 					return new DollarExpressionAst(UNARY_FACTOR(allowComparison, allowInKeyword, allowMultidimIndices));
1466 				}
1467 				return new DollarExpressionAst(FACTOR(allowComparison, allowInKeyword, allowMultidimIndices));
1468 			}
1469 			return SYMBOL(allowComparison, allowInKeyword);
1470 		}
1471 	}
1472 
1473 	// SYMBOL : Token.ID [ '(' params ')' | '[' ASSIGNMENT_EXPRESSION ']' ]
1474 	AST SYMBOL(boolean allowComparison, boolean allowInKeyword) throws IOException {
1475 		if (token != Token.ID && token != Token.FUNC_ID && token != Token.BUILTIN_FUNC_NAME && token != Token.EXTENSION) {
1476 			throw parserException("Expecting an Token.ID. Got " + token.name() + ": " + text);
1477 		}
1478 		Token idToken = token;
1479 		String id = text.toString();
1480 		boolean parens = c == '(';
1481 		lexer();
1482 
1483 		if (idToken == Token.EXTENSION) {
1484 			String extensionKeyword = id;
1485 			ExtensionFunction function = extensions.get(extensionKeyword);
1486 			if (function == null) {
1487 				throw parserException("Unknown extension keyword: " + extensionKeyword);
1488 			}
1489 			AST params;
1490 
1491 			/*
1492 			 * if (extension.requiresParen()) {
1493 			 * lexer(Token.OPEN_PAREN);
1494 			 * if (token == Token.CLOSE_PAREN)
1495 			 * params = null;
1496 			 * else
1497 			 * params = EXPRESSION_LIST(allowComparison, allowInKeyword);
1498 			 * lexer(Token.CLOSE_PAREN);
1499 			 * } else {
1500 			 * boolean parens = c == '(';
1501 			 * //expectKeyword("delete");
1502 			 * if (parens) {
1503 			 * assert token == Token.OPEN_PAREN;
1504 			 * lexer();
1505 			 * }
1506 			 * //AST symbolAst = SYMBOL(true,true); // allow comparators
1507 			 * params = EXPRESSION_LIST(allowComparison, allowInKeyword);
1508 			 * if (parens)
1509 			 * lexer(Token.CLOSE_PAREN);
1510 			 * }
1511 			 */
1512 
1513 			// if (extension.requiresParens() || parens)
1514 			if (parens) {
1515 				lexer();
1516 				if (token == Token.CLOSE_PAREN) {
1517 					params = null;
1518 				} else { // comparators allowed, allow “in” inside the extension call
1519 					params = EXPRESSION_LIST(true, allowInKeyword);
1520 				}
1521 				lexer(Token.CLOSE_PAREN);
1522 			} else {
1523 				/*
1524 				 * if (token == Token.NEWLINE || token == Token.SEMICOLON || token == Token.CLOSE_BRACE || token ==
1525 				 * Token.CLOSE_PAREN
1526 				 * || (token == Token.GT || token == Token.APPEND || token == Token.PIPE) )
1527 				 * params = null;
1528 				 * else
1529 				 * params = EXPRESSION_LIST(false,true);
1530 				 */
1531 				params = null;
1532 			}
1533 
1534 			return new ExtensionAst(function, params);
1535 		} else if (idToken == Token.FUNC_ID || idToken == Token.BUILTIN_FUNC_NAME) {
1536 			AST params;
1537 			// length can take on the special form of no parens
1538 			if (id.equals("length")) {
1539 				if (token == Token.OPEN_PAREN) {
1540 					lexer();
1541 					if (token == Token.CLOSE_PAREN) {
1542 						params = null;
1543 					} else {
1544 						params = EXPRESSION_LIST(true, allowInKeyword);
1545 					}
1546 					lexer(Token.CLOSE_PAREN);
1547 				} else {
1548 					params = null;
1549 				}
1550 			} else {
1551 				lexer(Token.OPEN_PAREN);
1552 				if (token == Token.CLOSE_PAREN) {
1553 					params = null;
1554 				} else {
1555 					params = EXPRESSION_LIST(true, allowInKeyword);
1556 				}
1557 				lexer(Token.CLOSE_PAREN);
1558 			}
1559 			if (idToken == Token.BUILTIN_FUNC_NAME) {
1560 				return new BuiltinFunctionCallAst(id, params);
1561 			} else {
1562 				return symbolTable.addFunctionCall(id, params);
1563 			}
1564 		}
1565 		if (token == Token.OPEN_BRACKET) {
1566 			int arrayReferenceLineNo = currentSourceLineNumber();
1567 			lexer();
1568 			AST idxAst = ARRAY_INDEX(true, allowInKeyword);
1569 			lexer(Token.CLOSE_BRACKET);
1570 			AST arrayReference = symbolTable.addArrayReference(id, idxAst, arrayReferenceLineNo);
1571 			if (!allowArraysOfArrays && token == Token.OPEN_BRACKET) {
1572 				throw parserException("Use [a,b,c,...] instead of [a][b][c]... for multi-dimensional arrays.");
1573 			}
1574 			while (allowArraysOfArrays && token == Token.OPEN_BRACKET) {
1575 				int nestedArrayReferenceLineNo = currentSourceLineNumber();
1576 				lexer();
1577 				idxAst = ARRAY_INDEX(true, allowInKeyword);
1578 				lexer(Token.CLOSE_BRACKET);
1579 				arrayReference = new ArrayReferenceAst(nestedArrayReferenceLineNo, arrayReference, idxAst);
1580 			}
1581 			return arrayReference;
1582 		}
1583 		return symbolTable.addID(id);
1584 	}
1585 
1586 	// ARRAY_INDEX : ASSIGNMENT_EXPRESSION [, ARRAY_INDEX]
1587 	AST ARRAY_INDEX(boolean allowComparison, boolean allowInKeyword) throws IOException {
1588 		AST exprAst = ASSIGNMENT_EXPRESSION(null, allowComparison, allowInKeyword, false);
1589 		if (token == Token.COMMA) {
1590 			optNewline();
1591 			lexer();
1592 			return new ArrayIndexAst(exprAst, ARRAY_INDEX(allowComparison, allowInKeyword));
1593 		} else {
1594 			return new ArrayIndexAst(exprAst, null);
1595 		}
1596 	}
1597 
1598 	// STATEMENT :
1599 	// IF_STATEMENT
1600 	// | WHILE_STATEMENT
1601 	// | FOR_STATEMENT
1602 	// | DO_STATEMENT
1603 	// | RETURN_STATEMENT
1604 	// | ASSIGNMENT_EXPRESSION
1605 	AST STATEMENT() throws IOException {
1606 		if (token == Token.OPEN_BRACE) {
1607 			lexer();
1608 			AST lst = STATEMENT_LIST();
1609 			lexer(Token.CLOSE_BRACE);
1610 			return lst;
1611 		}
1612 		AST stmt;
1613 		if (token == Token.KW_IF) {
1614 			stmt = IF_STATEMENT();
1615 		} else if (token == Token.KW_WHILE) {
1616 			stmt = WHILE_STATEMENT();
1617 		} else if (token == Token.KW_FOR) {
1618 			stmt = FOR_STATEMENT();
1619 		} else {
1620 			if (token == Token.KW_DO) {
1621 				stmt = DO_STATEMENT();
1622 			} else if (token == Token.KW_RETURN) {
1623 				stmt = RETURN_STATEMENT();
1624 			} else if (token == Token.KW_EXIT) {
1625 				stmt = EXIT_STATEMENT();
1626 			} else if (token == Token.KW_DELETE) {
1627 				stmt = DELETE_STATEMENT();
1628 			} else if (token == Token.KW_PRINT) {
1629 				stmt = PRINT_STATEMENT();
1630 			} else if (token == Token.KW_PRINTF) {
1631 				stmt = PRINTF_STATEMENT();
1632 			} else if (token == Token.KW_NEXT) {
1633 				stmt = NEXT_STATEMENT();
1634 			} else if (token == Token.KW_CONTINUE) {
1635 				stmt = CONTINUE_STATEMENT();
1636 			} else if (token == Token.KW_BREAK) {
1637 				stmt = BREAK_STATEMENT();
1638 			} else {
1639 				stmt = EXPRESSION_STATEMENT(true, false); // allow in keyword, do Token.NOT allow non-statement ASTs
1640 			}
1641 			terminator();
1642 			return stmt;
1643 		}
1644 		// NO TERMINATOR FOR IF, WHILE, Token.AND FOR
1645 		// (leave it for absorption by the callee)
1646 		return stmt;
1647 	}
1648 
1649 	AST EXPRESSION_STATEMENT(boolean allowInKeyword, boolean allowNonStatementAsts) throws IOException {
1650 		// true = allow comparators
1651 		// false = do Token.NOT allow multi-dimensional array indices
1652 		// return new ExpressionStatementAst(ASSIGNMENT_EXPRESSION(true, allowInKeyword, false));
1653 
1654 		AST exprAst = ASSIGNMENT_EXPRESSION(null, true, allowInKeyword, false);
1655 		if (!allowNonStatementAsts && exprAst.hasFlag(AstFlag.NON_STATEMENT)) {
1656 			throw parserException("Not a valid statement.");
1657 		}
1658 		return new ExpressionStatementAst(exprAst);
1659 	}
1660 
1661 	AST IF_STATEMENT() throws IOException {
1662 		expectKeyword("if");
1663 		lexer(Token.OPEN_PAREN);
1664 		AST expr = ASSIGNMENT_EXPRESSION(null, true, true, false); // allow comparators, allow in keyword, do Token.NOT
1665 																																// allow
1666 		// multidim
1667 		// indices expressions
1668 		lexer(Token.CLOSE_PAREN);
1669 
1670 		//// Was:
1671 		//// AST b1 = BLOCK_OR_STMT();
1672 		//// But it didn't handle
1673 		//// if ; else ...
1674 		//// properly
1675 		optNewline();
1676 		AST b1;
1677 		if (token == Token.SEMICOLON) {
1678 			lexer();
1679 			// consume the newline after the semicolon
1680 			optNewline();
1681 			b1 = null;
1682 		} else {
1683 			b1 = BLOCK_OR_STMT();
1684 		}
1685 
1686 		// The OPT_NEWLINE() above causes issues with the following form:
1687 		// if (...) {
1688 		// }
1689 		// else { ... }
1690 		// The \n before the else disassociates subsequent statements
1691 		// if an "else" does not immediately follow.
1692 		// To accommodate, the ifStatement will continue to manage
1693 		// statements, causing the original OPT_STATEMENT_LIST to relinquish
1694 		// processing statements to this OPT_STATEMENT_LIST.
1695 
1696 		optNewline();
1697 		if (token == Token.KW_ELSE) {
1698 			lexer();
1699 			optNewline();
1700 			AST b2 = BLOCK_OR_STMT();
1701 			return new IfStatementAst(expr, b1, b2);
1702 		} else {
1703 			AST ifAst = new IfStatementAst(expr, b1, null);
1704 			return ifAst;
1705 		}
1706 	}
1707 
1708 	AST BREAK_STATEMENT() throws IOException {
1709 		expectKeyword("break");
1710 		return new BreakStatementAst();
1711 	}
1712 
1713 	AST BLOCK_OR_STMT() throws IOException {
1714 		// default case, does Token.NOT consume (require) a terminator
1715 		return BLOCK_OR_STMT(false);
1716 	}
1717 
1718 	AST BLOCK_OR_STMT(boolean requireTerminator) throws IOException {
1719 		optNewline();
1720 		AST block;
1721 		// HIJACK BRACES HERE SINCE WE MAY Token.NOT HAVE A TERMINATOR AFTER THE CLOSING BRACE
1722 		if (token == Token.OPEN_BRACE) {
1723 			lexer();
1724 			block = STATEMENT_LIST();
1725 			lexer(Token.CLOSE_BRACE);
1726 			return block;
1727 		} else if (token == Token.SEMICOLON) {
1728 			block = null;
1729 		} else {
1730 			block = STATEMENT();
1731 			// NO TERMINATOR HERE!
1732 		}
1733 		if (requireTerminator) {
1734 			terminator();
1735 		}
1736 		return block;
1737 	}
1738 
1739 	AST WHILE_STATEMENT() throws IOException {
1740 		expectKeyword("while");
1741 		lexer(Token.OPEN_PAREN);
1742 		AST expr = ASSIGNMENT_EXPRESSION(null, true, true, false); // allow comparators, allow IN keyword, do Token.NOT
1743 																																// allow
1744 		// multidim
1745 		// indices expressions
1746 		lexer(Token.CLOSE_PAREN);
1747 		AST block = BLOCK_OR_STMT();
1748 		return new WhileStatementAst(expr, block);
1749 	}
1750 
1751 	AST FOR_STATEMENT() throws IOException {
1752 		expectKeyword("for");
1753 		AST expr1 = null;
1754 		AST expr2 = null;
1755 		AST expr3 = null;
1756 		lexer(Token.OPEN_PAREN);
1757 		expr1 = OPT_SIMPLE_STATEMENT(false); // false = "no in keyword allowed"
1758 
1759 		// branch here if we expect a for(... in ...) statement
1760 		if (token == Token.KW_IN) {
1761 			if (expr1.ast1 == null || expr1.ast2 != null) {
1762 				throw parserException("Invalid expression prior to 'in' statement. Got : " + expr1);
1763 			}
1764 			expr1 = expr1.ast1;
1765 			// analyze expr1 to make sure it's a singleton IDAst
1766 			if (!(expr1 instanceof IDAst)) {
1767 				throw parserException("Expecting an Token.ID for 'in' statement. Got : " + expr1);
1768 			}
1769 			// in
1770 			lexer();
1771 			if (token != Token.ID) {
1772 				throw parserException(
1773 						"Expecting an array or subarray for 'in' statement. Got " + token.name() + ": " + text);
1774 			}
1775 			AST arrayAst = SYMBOL(true, true);
1776 			// close paren ...
1777 			lexer(Token.CLOSE_PAREN);
1778 			AST block = BLOCK_OR_STMT();
1779 			return new ForInStatementAst(expr1, arrayAst, block);
1780 		}
1781 
1782 		if (token == Token.SEMICOLON) {
1783 			lexer();
1784 			optNewline();
1785 		} else {
1786 			throw parserException("Expecting ;. Got " + token.name() + ": " + text);
1787 		}
1788 		if (token != Token.SEMICOLON) {
1789 			expr2 = ASSIGNMENT_EXPRESSION(null, true, true, false); // allow comparators, allow IN keyword, do Token.NOT allow
1790 			// multidim
1791 			// indices expressions
1792 		}
1793 		if (token == Token.SEMICOLON) {
1794 			lexer();
1795 			optNewline();
1796 		} else {
1797 			throw parserException("Expecting ;. Got " + token.name() + ": " + text);
1798 		}
1799 		if (token != Token.CLOSE_PAREN) {
1800 			expr3 = OPT_SIMPLE_STATEMENT(true); // true = "allow the in keyword"
1801 		}
1802 		lexer(Token.CLOSE_PAREN);
1803 		AST block = BLOCK_OR_STMT();
1804 		return new ForStatementAst(expr1, expr2, expr3, block);
1805 	}
1806 
1807 	AST OPT_SIMPLE_STATEMENT(boolean allowInKeyword) throws IOException {
1808 		if (token == Token.SEMICOLON) {
1809 			return null;
1810 		} else if (token == Token.KW_DELETE) {
1811 			return DELETE_STATEMENT();
1812 		} else if (token == Token.KW_PRINT) {
1813 			return PRINT_STATEMENT();
1814 		} else if (token == Token.KW_PRINTF) {
1815 			return PRINTF_STATEMENT();
1816 		} else {
1817 			// allow non-statement ASTs
1818 			return EXPRESSION_STATEMENT(allowInKeyword, true);
1819 		}
1820 	}
1821 
1822 	AST DELETE_STATEMENT() throws IOException {
1823 		boolean parens = c == '(';
1824 		expectKeyword("delete");
1825 		if (parens) {
1826 			lexer();
1827 		}
1828 		AST symbolAst = SYMBOL(true, true); // allow comparators
1829 		if (parens) {
1830 			lexer(Token.CLOSE_PAREN);
1831 		}
1832 
1833 		return new DeleteStatementAst(symbolAst);
1834 	}
1835 
1836 	private static final class ParsedPrintStatement {
1837 
1838 		private final AST funcParams;
1839 		private final Token outputToken;
1840 		private final AST outputExpr;
1841 		private final boolean parenthesized;
1842 
1843 		ParsedPrintStatement(AST funcParams, Token outputToken, AST outputExpr, boolean parenthesized) {
1844 			this.funcParams = funcParams;
1845 			this.outputToken = outputToken;
1846 			this.outputExpr = outputExpr;
1847 			this.parenthesized = parenthesized;
1848 		}
1849 
1850 		public AST getFuncParams() {
1851 			return funcParams;
1852 		}
1853 
1854 		public Token getOutputToken() {
1855 			return outputToken;
1856 		}
1857 
1858 		public AST getOutputExpr() {
1859 			return outputExpr;
1860 		}
1861 
1862 		public boolean isParenthesized() {
1863 			return parenthesized;
1864 		}
1865 	}
1866 
1867 	private ParsedPrintStatement parsePrintStatement() throws IOException {
1868 		AST funcParams;
1869 		Token outputToken;
1870 		AST outputExpr;
1871 		boolean parenthesized = false;
1872 
1873 		if (token == Token.OPEN_PAREN) {
1874 			parenthesized = true;
1875 			funcParams = parseParenthesizedPrintArguments();
1876 		} else if (endsPrintArgumentList(token)) {
1877 			funcParams = null;
1878 		} else {
1879 			funcParams = EXPRESSION_LIST(false, true); // no comparisons allowed, but allow “in”
1880 		}
1881 
1882 		if (token == Token.GT || token == Token.APPEND || token == Token.PIPE) {
1883 			outputToken = token;
1884 			lexer();
1885 			outputExpr = ASSIGNMENT_EXPRESSION(null, true, true, false); // allow comparisons, “in”; no multidim indices
1886 		} else {
1887 			outputToken = null;
1888 			outputExpr = null;
1889 		}
1890 
1891 		return new ParsedPrintStatement(funcParams, outputToken, outputExpr, parenthesized);
1892 	}
1893 
1894 	private boolean endsPrintArgumentList(Token candidate) {
1895 		return candidate == Token.NEWLINE
1896 				|| candidate == Token.SEMICOLON
1897 				|| candidate == Token.CLOSE_BRACE
1898 				|| candidate == Token.CLOSE_PAREN
1899 				|| candidate == Token.GT
1900 				|| candidate == Token.APPEND
1901 				|| candidate == Token.PIPE
1902 				|| candidate == Token.EOF;
1903 	}
1904 
1905 	private AST parseParenthesizedPrintArguments() throws IOException {
1906 		lexer(); // consume '('
1907 		if (token == Token.CLOSE_PAREN) {
1908 			lexer(); // consume ')'
1909 			return null;
1910 		}
1911 
1912 		AST params = EXPRESSION_LIST(true, true); // allow comparisons and “in” within parentheses
1913 		lexer(Token.CLOSE_PAREN);
1914 
1915 		if (params instanceof FunctionCallParamListAst) {
1916 			FunctionCallParamListAst paramList = (FunctionCallParamListAst) params;
1917 			if (paramList.getAst2() == null && !endsPrintArgumentList(token)) {
1918 				AST continuedExpression = ASSIGNMENT_EXPRESSION(
1919 						paramList.getAst1(),
1920 						false,
1921 						true,
1922 						false);
1923 				return new FunctionCallParamListAst(continuedExpression, null);
1924 			}
1925 		}
1926 
1927 		return params;
1928 	}
1929 
1930 	AST PRINT_STATEMENT() throws IOException {
1931 		expectKeyword("print");
1932 		ParsedPrintStatement parsedPrintStatement = parsePrintStatement();
1933 
1934 		AST params = parsedPrintStatement.getFuncParams();
1935 		if (parsedPrintStatement.isParenthesized()
1936 				&& token == Token.QUESTION_MARK
1937 				&& params instanceof FunctionCallParamListAst
1938 				&& ((FunctionCallParamListAst) params).getAst2() == null) {
1939 			AST condExpr = ((FunctionCallParamListAst) params).getAst1();
1940 			lexer();
1941 			AST trueBlock = TERNARY_EXPRESSION(null, true, true, true);
1942 			lexer(Token.COLON);
1943 			AST falseBlock = TERNARY_EXPRESSION(null, true, true, true);
1944 			params = new FunctionCallParamListAst(
1945 					new TernaryExpressionAst(condExpr, trueBlock, falseBlock),
1946 					null);
1947 		}
1948 
1949 		return new PrintAst(
1950 				params,
1951 				parsedPrintStatement.getOutputToken(),
1952 				parsedPrintStatement.getOutputExpr(),
1953 				parsedPrintStatement.isParenthesized());
1954 	}
1955 
1956 	AST PRINTF_STATEMENT() throws IOException {
1957 		expectKeyword("printf");
1958 		ParsedPrintStatement parsedPrintStatement = parsePrintStatement();
1959 
1960 		AST params = parsedPrintStatement.getFuncParams();
1961 		if (parsedPrintStatement.isParenthesized()
1962 				&& token == Token.QUESTION_MARK
1963 				&& params instanceof FunctionCallParamListAst
1964 				&& ((FunctionCallParamListAst) params).getAst2() == null) {
1965 			AST condExpr = ((FunctionCallParamListAst) params).getAst1();
1966 			lexer();
1967 			AST trueBlock = TERNARY_EXPRESSION(null, true, true, true);
1968 			lexer(Token.COLON);
1969 			AST falseBlock = TERNARY_EXPRESSION(null, true, true, true);
1970 			params = new FunctionCallParamListAst(
1971 					new TernaryExpressionAst(condExpr, trueBlock, falseBlock),
1972 					null);
1973 		}
1974 
1975 		return new PrintfAst(
1976 				params,
1977 				parsedPrintStatement.getOutputToken(),
1978 				parsedPrintStatement.getOutputExpr());
1979 	}
1980 
1981 	AST GETLINE_EXPRESSION(AST pipeExpr, boolean allowComparison, boolean allowInKeyword) throws IOException {
1982 		expectKeyword("getline");
1983 		AST lvalue = LVALUE(allowComparison, allowInKeyword);
1984 		if (token == Token.LT) {
1985 			lexer();
1986 			AST assignmentExpr = ASSIGNMENT_EXPRESSION(null, allowComparison, allowInKeyword, false); // do Token.NOT allow
1987 																																																// multidim
1988 			// indices expressions
1989 			return pipeExpr == null ?
1990 					new GetlineAst(null, lvalue, assignmentExpr) : new GetlineAst(pipeExpr, lvalue, assignmentExpr);
1991 		} else {
1992 			return pipeExpr == null ? new GetlineAst(null, lvalue, null) : new GetlineAst(pipeExpr, lvalue, null);
1993 		}
1994 	}
1995 
1996 	AST LVALUE(boolean allowComparison, boolean allowInKeyword) throws IOException {
1997 		// false = do Token.NOT allow multi dimension indices expressions
1998 		if (token == Token.DOLLAR) {
1999 			return FACTOR(allowComparison, allowInKeyword, false);
2000 		}
2001 		if (token == Token.ID) {
2002 			return FACTOR(allowComparison, allowInKeyword, false);
2003 		}
2004 		return null;
2005 	}
2006 
2007 	AST DO_STATEMENT() throws IOException {
2008 		expectKeyword("do");
2009 		optNewline();
2010 		AST block = BLOCK_OR_STMT();
2011 		if (token == Token.SEMICOLON) {
2012 			lexer();
2013 		}
2014 		optNewline();
2015 		expectKeyword("while");
2016 		lexer(Token.OPEN_PAREN);
2017 		AST expr = ASSIGNMENT_EXPRESSION(null, true, true, false); // true = allow comparators, allow IN keyword, do
2018 																																// Token.NOT
2019 		// allow
2020 		// multidim indices expressions
2021 		lexer(Token.CLOSE_PAREN);
2022 		return new DoStatementAst(block, expr);
2023 	}
2024 
2025 	AST RETURN_STATEMENT() throws IOException {
2026 		expectKeyword("return");
2027 		if (token == Token.SEMICOLON || token == Token.NEWLINE || token == Token.CLOSE_BRACE) {
2028 			return new ReturnStatementAst(null);
2029 		} else {
2030 			return new ReturnStatementAst(ASSIGNMENT_EXPRESSION(null, true, true, false)); // true = allow comparators, allow
2031 																																											// IN
2032 			// keyword, do Token.NOT allow multidim
2033 			// indices expressions
2034 		}
2035 	}
2036 
2037 	AST EXIT_STATEMENT() throws IOException {
2038 		expectKeyword("exit");
2039 		if (token == Token.SEMICOLON || token == Token.NEWLINE || token == Token.CLOSE_BRACE) {
2040 			return new ExitStatementAst(null);
2041 		} else {
2042 			return new ExitStatementAst(ASSIGNMENT_EXPRESSION(null, true, true, false)); // true = allow comparators, allow IN
2043 			// keyword, do Token.NOT allow multidim
2044 			// indices
2045 			// expressions
2046 		}
2047 	}
2048 
2049 	AST NEXT_STATEMENT() throws IOException {
2050 		expectKeyword("next");
2051 		return new NextStatementAst();
2052 	}
2053 
2054 	AST CONTINUE_STATEMENT() throws IOException {
2055 		expectKeyword("continue");
2056 		return new ContinueStatementAst();
2057 	}
2058 
2059 	// CHECKSTYLE.ON MethodName
2060 
2061 	private void expectKeyword(String keyword) throws IOException {
2062 		if (token == KEYWORDS.get(keyword)) {
2063 			lexer();
2064 		} else {
2065 			throw parserException("Expecting " + keyword + ". Got " + token.name() + ": " + text);
2066 		}
2067 	}
2068 
2069 	private void populateArrayOperandTuples(
2070 			AST arrayAst,
2071 			AwkTuples tuples,
2072 			boolean createIfMissing,
2073 			String errorMessage) {
2074 		if (arrayAst instanceof IDAst) {
2075 			IDAst idAst = (IDAst) arrayAst;
2076 			if (idAst.isScalar()) {
2077 				arrayAst.throwSemanticException(errorMessage);
2078 			}
2079 			idAst.setArray(true);
2080 			idAst.populateTuples(tuples);
2081 			return;
2082 		}
2083 		if (arrayAst instanceof ArrayReferenceAst) {
2084 			if (!allowArraysOfArrays) {
2085 				arrayAst.throwSemanticException(errorMessage);
2086 			}
2087 			((ArrayReferenceAst) arrayAst).populateArrayValueTuples(tuples, createIfMissing);
2088 			return;
2089 		}
2090 		arrayAst.throwSemanticException(errorMessage);
2091 	}
2092 
2093 	private int populateActualParameters(
2094 			AwkTuples tuples,
2095 			FunctionCallParamListAst params,
2096 			Set<Integer> arrayParameterIndexes,
2097 			int parameterIndex) {
2098 		if (params == null) {
2099 			return 0;
2100 		}
2101 		if (arrayParameterIndexes.contains(Integer.valueOf(parameterIndex))) {
2102 			populateArrayOperandTuples(
2103 					params.getAst1(),
2104 					tuples,
2105 					true,
2106 					"Parameter position " + (parameterIndex + 1) + " must be an array or subarray.");
2107 		} else {
2108 			params.getAst1().populateTuples(tuples);
2109 		}
2110 		if (params.getAst2() == null) {
2111 			return 1;
2112 		}
2113 		return 1 + populateActualParameters(
2114 				tuples,
2115 				(FunctionCallParamListAst) params.getAst2(),
2116 				arrayParameterIndexes,
2117 				parameterIndex + 1);
2118 	}
2119 
2120 	private Set<Integer> collectArrayParameterIndexes(FunctionDefAst functionDefAst) {
2121 		Set<Integer> arrayIndexes = new HashSet<Integer>();
2122 		FunctionDefParamListAst fPtr = (FunctionDefParamListAst) functionDefAst.getAst1();
2123 		int index = 0;
2124 		while (fPtr != null) {
2125 			IDAst fparam = symbolTable.getFunctionParameterIDAST(functionDefAst.id, fPtr.id);
2126 			if (fparam.isArray()) {
2127 				arrayIndexes.add(Integer.valueOf(index));
2128 			}
2129 			fPtr = (FunctionDefParamListAst) fPtr.getAst1();
2130 			index++;
2131 		}
2132 		return arrayIndexes;
2133 	}
2134 
2135 	// parser
2136 	// ===============================================================================
2137 	// AST class defs
2138 	private abstract class AST extends AstNode {
2139 
2140 		private final String sourceDescription = scriptSources.get(scriptSourcesCurrentIndex).getDescription();
2141 		// PositionTracker consumes these tuple-emitted source lines at runtime, but
2142 		// AST nodes have to capture them here during parsing before tuples exist.
2143 		private final int lineNo;
2144 		private AST parent;
2145 		private AST ast1, ast2, ast3, ast4;
2146 		private final EnumSet<AstFlag> flags = EnumSet.noneOf(AstFlag.class);
2147 
2148 		protected final void addFlag(AstFlag flag) {
2149 			flags.add(flag);
2150 		}
2151 
2152 		protected final boolean hasFlag(AstFlag flag) {
2153 			return flags.contains(flag);
2154 		}
2155 
2156 		protected Address breakAddress() {
2157 			return null;
2158 		}
2159 
2160 		protected Address continueAddress() {
2161 			return null;
2162 		}
2163 
2164 		protected Address nextAddress() {
2165 			return null;
2166 		}
2167 
2168 		protected Address returnAddress() {
2169 			return null;
2170 		}
2171 
2172 		protected final AST getParent() {
2173 			return parent;
2174 		}
2175 
2176 		@SuppressWarnings("unused")
2177 		protected final void setParent(AST p) {
2178 			parent = p;
2179 		}
2180 
2181 		protected final AST getAst1() {
2182 			return ast1;
2183 		}
2184 
2185 		@SuppressWarnings("unused")
2186 		protected final void setAst1(AST a1) {
2187 			ast1 = a1;
2188 		}
2189 
2190 		protected final AST getAst2() {
2191 			return ast2;
2192 		}
2193 
2194 		@SuppressWarnings("unused")
2195 		protected final void setAst2(AST a2) {
2196 			ast2 = a2;
2197 		}
2198 
2199 		protected final AST getAst3() {
2200 			return ast3;
2201 		}
2202 
2203 		@SuppressWarnings("unused")
2204 		protected final void setAst3(AST a3) {
2205 			ast3 = a3;
2206 		}
2207 
2208 		protected final AST getAst4() {
2209 			return ast4;
2210 		}
2211 
2212 		@SuppressWarnings("unused")
2213 		protected final void setAst4(AST a4) {
2214 			ast4 = a4;
2215 		}
2216 
2217 		protected final AST searchFor(AstFlag flag) {
2218 			AST ptr = this;
2219 			while (ptr != null) {
2220 				if (ptr.hasFlag(flag)) {
2221 					return ptr;
2222 				}
2223 				ptr = ptr.parent;
2224 			}
2225 			return null;
2226 		}
2227 
2228 		protected AST() {
2229 			this(currentSourceLineNumber());
2230 		}
2231 
2232 		protected AST(int lineNo) {
2233 			this.lineNo = lineNo;
2234 		}
2235 
2236 		protected AST(AST ast1) {
2237 			this(currentSourceLineNumber(), ast1);
2238 		}
2239 
2240 		protected AST(int lineNo, AST ast1) {
2241 			this(lineNo);
2242 			this.ast1 = ast1;
2243 
2244 			if (ast1 != null) {
2245 				ast1.parent = this;
2246 			}
2247 		}
2248 
2249 		protected AST(AST ast1, AST ast2) {
2250 			this(currentSourceLineNumber(), ast1, ast2);
2251 		}
2252 
2253 		protected AST(int lineNo, AST ast1, AST ast2) {
2254 			this(lineNo);
2255 			this.ast1 = ast1;
2256 			this.ast2 = ast2;
2257 
2258 			if (ast1 != null) {
2259 				ast1.parent = this;
2260 			}
2261 			if (ast2 != null) {
2262 				ast2.parent = this;
2263 			}
2264 		}
2265 
2266 		protected AST(AST ast1, AST ast2, AST ast3) {
2267 			this(currentSourceLineNumber(), ast1, ast2, ast3);
2268 		}
2269 
2270 		protected AST(int lineNo, AST ast1, AST ast2, AST ast3) {
2271 			this(lineNo);
2272 			this.ast1 = ast1;
2273 			this.ast2 = ast2;
2274 			this.ast3 = ast3;
2275 
2276 			if (ast1 != null) {
2277 				ast1.parent = this;
2278 			}
2279 			if (ast2 != null) {
2280 				ast2.parent = this;
2281 			}
2282 			if (ast3 != null) {
2283 				ast3.parent = this;
2284 			}
2285 		}
2286 
2287 		protected AST(AST ast1, AST ast2, AST ast3, AST ast4) {
2288 			this(currentSourceLineNumber(), ast1, ast2, ast3, ast4);
2289 		}
2290 
2291 		protected AST(int lineNo, AST ast1, AST ast2, AST ast3, AST ast4) {
2292 			this(lineNo);
2293 			this.ast1 = ast1;
2294 			this.ast2 = ast2;
2295 			this.ast3 = ast3;
2296 			this.ast4 = ast4;
2297 
2298 			if (ast1 != null) {
2299 				ast1.parent = this;
2300 			}
2301 			if (ast2 != null) {
2302 				ast2.parent = this;
2303 			}
2304 			if (ast3 != null) {
2305 				ast3.parent = this;
2306 			}
2307 			if (ast4 != null) {
2308 				ast4.parent = this;
2309 			}
2310 		}
2311 
2312 		/**
2313 		 * Dump a meaningful text representation of this
2314 		 * abstract syntax tree node to the output (print)
2315 		 * stream. Either it is called directly by the
2316 		 * application program, or it is called by the
2317 		 * parent node of this tree node.
2318 		 *
2319 		 * @param ps The print stream to dump the text
2320 		 *        representation.
2321 		 */
2322 		@Override
2323 		public void dump(PrintStream ps) {
2324 			dump(ps, 0);
2325 		}
2326 
2327 		private void dump(PrintStream ps, int lvl) {
2328 			StringBuffer spaces = new StringBuffer();
2329 			for (int i = 0; i < lvl; i++) {
2330 				spaces.append(' ');
2331 			}
2332 			ps.println(spaces + toString());
2333 			if (ast1 != null) {
2334 				ast1.dump(ps, lvl + 1);
2335 			}
2336 			if (ast2 != null) {
2337 				ast2.dump(ps, lvl + 1);
2338 			}
2339 			if (ast3 != null) {
2340 				ast3.dump(ps, lvl + 1);
2341 			}
2342 			if (ast4 != null) {
2343 				ast4.dump(ps, lvl + 1);
2344 			}
2345 		}
2346 
2347 		/**
2348 		 * Apply semantic checks to this node. The default
2349 		 * implementation is to simply call semanticAnalysis()
2350 		 * on all the children of this abstract syntax tree node.
2351 		 * Therefore, this method must be overridden to provide
2352 		 * meaningful semantic analysis / checks.
2353 		 *
2354 		 * @throws SemanticException upon a semantic error.
2355 		 */
2356 		@Override
2357 		public void semanticAnalysis() {
2358 			if (ast1 != null) {
2359 				ast1.semanticAnalysis();
2360 			}
2361 			if (ast2 != null) {
2362 				ast2.semanticAnalysis();
2363 			}
2364 			if (ast3 != null) {
2365 				ast3.semanticAnalysis();
2366 			}
2367 			if (ast4 != null) {
2368 				ast4.semanticAnalysis();
2369 			}
2370 		}
2371 
2372 		/**
2373 		 * Appends tuples to the AwkTuples list
2374 		 * for this abstract syntax tree node. Subclasses
2375 		 * must implement this method.
2376 		 * <p>
2377 		 * This is called either by the main program to generate a full
2378 		 * list of tuples for the abstract syntax tree, or it is called
2379 		 * by other abstract syntax tree nodes in response to their
2380 		 * attempt at populating tuples.
2381 		 *
2382 		 * @param tuples The tuples to populate.
2383 		 * @return The number of items left on the stack after
2384 		 *         these tuples have executed.
2385 		 */
2386 		@Override
2387 		public abstract int populateTuples(AwkTuples tuples);
2388 
2389 		protected final void pushSourceLineNumber(AwkTuples tuples) {
2390 			tuples.pushSourceLineNumber(lineNo);
2391 		}
2392 
2393 		protected final void popSourceLineNumber(AwkTuples tuples) {
2394 			tuples.popSourceLineNumber(lineNo);
2395 		}
2396 
2397 		private boolean isBegin = isBegin();
2398 
2399 		@SuppressWarnings("unused")
2400 		protected final boolean isBeginFlag() {
2401 			return isBegin;
2402 		}
2403 
2404 		protected final void setBeginFlag(boolean flag) {
2405 			isBegin = flag;
2406 		}
2407 
2408 		private boolean isBegin() {
2409 			boolean result = isBegin;
2410 			if (!result && ast1 != null) {
2411 				result = ast1.isBegin();
2412 			}
2413 			if (!result && ast2 != null) {
2414 				result = ast2.isBegin();
2415 			}
2416 			if (!result && ast3 != null) {
2417 				result = ast3.isBegin();
2418 			}
2419 			if (!result && ast4 != null) {
2420 				result = ast4.isBegin();
2421 			}
2422 			return result;
2423 		}
2424 
2425 		private boolean isEnd = isEnd();
2426 
2427 		@SuppressWarnings("unused")
2428 		protected final boolean isEndFlag() {
2429 			return isEnd;
2430 		}
2431 
2432 		protected final void setEndFlag(boolean flag) {
2433 			isEnd = flag;
2434 		}
2435 
2436 		private boolean isEnd() {
2437 			boolean result = isEnd;
2438 			if (!result && ast1 != null) {
2439 				result = ast1.isEnd();
2440 			}
2441 			if (!result && ast2 != null) {
2442 				result = ast2.isEnd();
2443 			}
2444 			if (!result && ast3 != null) {
2445 				result = ast3.isEnd();
2446 			}
2447 			if (!result && getAst4() != null) {
2448 				result = getAst4().isEnd();
2449 			}
2450 			return result;
2451 		}
2452 
2453 		private boolean isFunction = isFunction();
2454 
2455 		@SuppressWarnings("unused")
2456 		protected final boolean isFunctionFlag() {
2457 			return isFunction;
2458 		}
2459 
2460 		protected final void setFunctionFlag(boolean flag) {
2461 			isFunction = flag;
2462 		}
2463 
2464 		private boolean isFunction() {
2465 			boolean result = isFunction;
2466 			if (!result && getAst1() != null) {
2467 				result = getAst1().isFunction();
2468 			}
2469 			if (!result && getAst2() != null) {
2470 				result = getAst2().isFunction();
2471 			}
2472 			if (!result && getAst3() != null) {
2473 				result = getAst3().isFunction();
2474 			}
2475 			if (!result && getAst4() != null) {
2476 				result = getAst4().isFunction();
2477 			}
2478 			return result;
2479 		}
2480 
2481 		public boolean isArray() {
2482 			return false;
2483 		}
2484 
2485 		public boolean isScalar() {
2486 			return false;
2487 		}
2488 
2489 		/**
2490 		 * Made protected so that subclasses can access it.
2491 		 * Package-level access was not necessary.
2492 		 */
2493 		protected class SemanticException extends RuntimeException {
2494 
2495 			private static final long serialVersionUID = 1L;
2496 
2497 			SemanticException(String msg) {
2498 				super(msg + " (" + sourceDescription + ":" + lineNo + ")");
2499 			}
2500 		}
2501 
2502 		protected final void throwSemanticException(String msg) {
2503 			throw new SemanticException(msg);
2504 		}
2505 
2506 		@Override
2507 		public String toString() {
2508 			return getClass().getName().replaceFirst(".*[$.]", "");
2509 		}
2510 	}
2511 
2512 	private abstract class ScalarExpressionAst extends AST {
2513 
2514 		protected ScalarExpressionAst() {
2515 			super();
2516 		}
2517 
2518 		protected ScalarExpressionAst(int lineNo) {
2519 			super(lineNo);
2520 		}
2521 
2522 		protected ScalarExpressionAst(AST a1) {
2523 			super(a1);
2524 		}
2525 
2526 		protected ScalarExpressionAst(int lineNo, AST a1) {
2527 			super(lineNo, a1);
2528 		}
2529 
2530 		protected ScalarExpressionAst(AST a1, AST a2) {
2531 			super(a1, a2);
2532 		}
2533 
2534 		protected ScalarExpressionAst(int lineNo, AST a1, AST a2) {
2535 			super(lineNo, a1, a2);
2536 		}
2537 
2538 		protected ScalarExpressionAst(AST a1, AST a2, AST a3) {
2539 			super(a1, a2, a3);
2540 		}
2541 
2542 		protected ScalarExpressionAst(int lineNo, AST a1, AST a2, AST a3) {
2543 			super(lineNo, a1, a2, a3);
2544 		}
2545 
2546 		@Override
2547 		public boolean isArray() {
2548 			return false;
2549 		}
2550 
2551 		@Override
2552 		public boolean isScalar() {
2553 			return true;
2554 		}
2555 	}
2556 
2557 	private static boolean isRule(AST ast) {
2558 		return ast != null && !ast.isBegin() && !ast.isEnd() && !ast.isFunction();
2559 	}
2560 
2561 	/**
2562 	 * Inspects the action rule condition whether it contains
2563 	 * extensions. It does a superficial check of
2564 	 * the abstract syntax tree of the action rule.
2565 	 * In other words, it will not examine whether user-defined
2566 	 * functions within the action rule contain extensions.
2567 	 *
2568 	 * @param ast The action rule expression to examine.
2569 	 * @return true if the action rule condition contains
2570 	 *         an extension; false otherwise.
2571 	 */
2572 	@SuppressWarnings("unused")
2573 	private static boolean isExtensionConditionRule(AST ast) {
2574 		if (!isRule(ast)) {
2575 			return false;
2576 		}
2577 		if (ast.getAst1() == null) {
2578 			return false;
2579 		}
2580 
2581 		if (!containsASTType(ast.getAst1(), ExtensionAst.class)) {
2582 			return false;
2583 		}
2584 
2585 		if (containsASTType(ast.getAst1(), new Class[] { FunctionCallAst.class, DollarExpressionAst.class })) {
2586 			return false;
2587 		}
2588 
2589 		return true;
2590 	}
2591 
2592 	private static boolean containsASTType(AST ast, Class<?> cls) {
2593 		return containsASTType(ast, new Class[] { cls });
2594 	}
2595 
2596 	private static boolean containsASTType(AST ast, Class<?>[] clsArray) {
2597 		if (ast == null) {
2598 			return false;
2599 		}
2600 		for (Class<?> cls : clsArray) {
2601 			if (cls.isInstance(ast)) {
2602 				return true;
2603 			}
2604 		}
2605 		// prettier-ignore
2606 		return containsASTType(ast.getAst1(), clsArray)
2607 				|| containsASTType(ast.getAst2(), clsArray)
2608 				|| containsASTType(ast.getAst3(), clsArray)
2609 				|| containsASTType(ast.getAst4(), clsArray);
2610 	}
2611 
2612 	private Address nextAddress;
2613 
2614 	private final class RuleListAst extends AST {
2615 
2616 		private RuleListAst(AST rule, AST rest) {
2617 			super(rule, rest);
2618 		}
2619 
2620 		@Override
2621 		public int populateTuples(AwkTuples tuples) {
2622 
2623 			pushSourceLineNumber(tuples);
2624 
2625 			nextAddress = tuples.createAddress("nextAddress");
2626 
2627 			// goto start address
2628 			Address startAddress = tuples.createAddress("start address");
2629 			tuples.gotoAddress(startAddress);
2630 
2631 			AST ptr;
2632 
2633 			// compile functions
2634 			ptr = this;
2635 			while (ptr != null) {
2636 				if (ptr.getAst1() != null && ptr.getAst1().isFunction()) {
2637 					ptr.getAst1().populateTuples(tuples);
2638 				}
2639 
2640 				ptr = ptr.getAst2();
2641 			}
2642 
2643 			// START OF MAIN BLOCK
2644 			tuples.address(startAddress);
2645 
2646 			// initialize runtime-managed special variables via JRT defaults in AVM
2647 			symbolTable.getID("NR");
2648 			symbolTable.getID("FNR");
2649 			symbolTable.getID("NF");
2650 			symbolTable.getID("FS");
2651 			symbolTable.getID("RS");
2652 			symbolTable.getID("OFS");
2653 			symbolTable.getID("ORS");
2654 			symbolTable.getID("RSTART");
2655 			symbolTable.getID("RLENGTH");
2656 			symbolTable.getID("FILENAME");
2657 			symbolTable.getID("SUBSEP");
2658 			symbolTable.getID("CONVFMT");
2659 			symbolTable.getID("OFMT");
2660 			IDAst environAst = symbolTable.getID("ENVIRON");
2661 			IDAst argcAst = symbolTable.getID("ARGC");
2662 			IDAst argvAst = symbolTable.getID("ARGV");
2663 
2664 			// MUST BE DONE AFTER FUNCTIONS ARE COMPILED,
2665 			// and after special variables are made known to the symbol table
2666 			// (see above)!
2667 			tuples.setNumGlobals(symbolTable.numGlobals());
2668 
2669 			// Only ENVIRON/ARGC/ARGV remain regular globals.
2670 			// Always materialize ARGC; ARGV remains lazily materialized.
2671 			if (environAst.isReferenced()) {
2672 				tuples.environOffset(environAst.offset);
2673 			}
2674 			tuples.argcOffset(argcAst.offset);
2675 			if (argvAst.isReferenced()) {
2676 				tuples.argvOffset(argvAst.offset);
2677 			}
2678 
2679 			Address exitAddr = tuples.createAddress("end blocks start address");
2680 			tuples.setExitAddress(exitAddr);
2681 
2682 			// grab all BEGINs
2683 			ptr = this;
2684 			// ptr.getAst1() == blank rule condition (i.e.: { print })
2685 			while (ptr != null) {
2686 				if (ptr.getAst1() != null && ptr.getAst1().isBegin()) {
2687 					ptr.getAst1().populateTuples(tuples);
2688 				}
2689 
2690 				ptr = ptr.getAst2();
2691 			}
2692 
2693 			// Do we have rules? (apart from BEGIN)
2694 			// If we have rules or END, we need to parse the input
2695 			boolean reqInput = false;
2696 
2697 			// Check for "normal" rules
2698 			ptr = this;
2699 			while (!reqInput && (ptr != null)) {
2700 				if (isRule(ptr.getAst1())) {
2701 					reqInput = true;
2702 				}
2703 				ptr = ptr.getAst2();
2704 			}
2705 
2706 			// Now check for "END" rules
2707 			ptr = this;
2708 			while (!reqInput && (ptr != null)) {
2709 				if (ptr.getAst1() != null && ptr.getAst1().isEnd()) {
2710 					reqInput = true;
2711 				}
2712 				ptr = ptr.getAst2();
2713 			}
2714 
2715 			if (reqInput) {
2716 				Address inputLoopAddress = null;
2717 				Address noMoreInput = null;
2718 
2719 				inputLoopAddress = tuples.createAddress("input_loop_address");
2720 				tuples.address(inputLoopAddress);
2721 
2722 				ptr = this;
2723 
2724 				noMoreInput = tuples.createAddress("no_more_input");
2725 				tuples.consumeInput(noMoreInput);
2726 
2727 				// grab all INPUT RULES
2728 				while (ptr != null) {
2729 					// the first one of these is an input rule
2730 					if (isRule(ptr.getAst1())) {
2731 						ptr.getAst1().populateTuples(tuples);
2732 					}
2733 					ptr = ptr.getAst2();
2734 				}
2735 				tuples.address(nextAddress);
2736 
2737 				tuples.gotoAddress(inputLoopAddress);
2738 
2739 				if (reqInput) {
2740 					tuples.address(noMoreInput);
2741 					// compiler has issue with missing nop here
2742 					tuples.nop();
2743 				}
2744 			}
2745 
2746 			// indicate where the first end block resides
2747 			// in the event of an exit statement
2748 			tuples.address(exitAddr);
2749 			tuples.setWithinEndBlocks(true);
2750 
2751 			// grab all ENDs
2752 			ptr = this;
2753 			while (ptr != null) {
2754 				if (ptr.getAst1() != null && ptr.getAst1().isEnd()) {
2755 					ptr.getAst1().populateTuples(tuples);
2756 				}
2757 				ptr = ptr.getAst2();
2758 			}
2759 
2760 			// force a nop here to resolve any addresses that haven't been resolved yet
2761 			// (i.e., no_more_input wouldn't be resolved if there are no END{} blocks)
2762 			tuples.nop();
2763 
2764 			popSourceLineNumber(tuples);
2765 			return 0;
2766 		}
2767 	}
2768 
2769 	private final class ExpressionToEvaluateAst extends AST {
2770 
2771 		private ExpressionToEvaluateAst(AST expr) {
2772 			super(expr);
2773 		}
2774 
2775 		@Override
2776 		public int populateTuples(AwkTuples tuples) {
2777 
2778 			pushSourceLineNumber(tuples);
2779 
2780 			// initialize runtime-managed special variables via JRT defaults in AVM
2781 			symbolTable.getID("NR");
2782 			symbolTable.getID("FNR");
2783 			symbolTable.getID("NF");
2784 			symbolTable.getID("FS");
2785 			symbolTable.getID("RS");
2786 			symbolTable.getID("SUBSEP");
2787 			symbolTable.getID("CONVFMT");
2788 			IDAst environAst = symbolTable.getID("ENVIRON");
2789 
2790 			// MUST BE DONE AFTER FUNCTIONS ARE COMPILED,
2791 			// and after special variables are made known to the symbol table
2792 			// (see above)!
2793 			tuples.markEvalTupleStream();
2794 			tuples.setNumGlobals(symbolTable.numGlobals());
2795 
2796 			if (environAst.isReferenced()) {
2797 				tuples.environOffset(environAst.offset);
2798 			}
2799 
2800 			if (getAst1() != null) {
2801 				getAst1().populateTuples(tuples);
2802 			}
2803 			// Some expression forms (for example ternaries) still need a concrete
2804 			// terminal tuple to resolve branch targets during post-processing.
2805 			tuples.nop();
2806 
2807 			popSourceLineNumber(tuples);
2808 			return 0;
2809 		}
2810 	}
2811 
2812 	// made non-static to access the "nextAddress" field of the frontend
2813 	private final class RuleAst extends AST {
2814 
2815 		private RuleAst(AST optExpression, AST optRule) {
2816 			super(optExpression, optRule);
2817 			addFlag(AstFlag.NEXTABLE);
2818 		}
2819 
2820 		@Override
2821 		public int populateTuples(AwkTuples tuples) {
2822 			pushSourceLineNumber(tuples);
2823 			boolean unconditionalRule = getAst1() == null
2824 					|| getAst1().isBegin()
2825 					|| getAst1().isEnd();
2826 			if (!unconditionalRule) {
2827 				getAst1().populateTuples(tuples);
2828 				// result of whether to execute or not is on the stack
2829 				Address bypassRule = tuples.createAddress("bypassRule");
2830 				tuples.ifFalse(bypassRule);
2831 				populateRuleBody(tuples);
2832 				tuples.address(bypassRule).nop();
2833 			} else {
2834 				populateRuleBody(tuples);
2835 			}
2836 			popSourceLineNumber(tuples);
2837 			return 0;
2838 		}
2839 
2840 		private void populateRuleBody(AwkTuples tuples) {
2841 			// execute the optRule here!
2842 			if (getAst2() == null) {
2843 				if (getAst1() == null || (!getAst1().isBegin() && !getAst1().isEnd())) {
2844 					// display $0
2845 					tuples.print(0);
2846 				}
2847 				// else, don't populate it with anything
2848 				// (i.e., blank BEGIN/END rule)
2849 			} else {
2850 				// execute it, and leave nothing on the stack
2851 				getAst2().populateTuples(tuples);
2852 			}
2853 		}
2854 
2855 		@Override
2856 		public Address nextAddress() {
2857 			if (!isRule(this)) {
2858 				throw new SemanticException("Must call next within an input rule.");
2859 			}
2860 			if (nextAddress == null) {
2861 				throw new SemanticException("Cannot call next here.");
2862 			}
2863 			return nextAddress;
2864 		}
2865 	}
2866 
2867 	private final class IfStatementAst extends AST {
2868 
2869 		private IfStatementAst(AST expr, AST b1, AST b2) {
2870 			super(expr, b1, b2);
2871 		}
2872 
2873 		@Override
2874 		public int populateTuples(AwkTuples tuples) {
2875 			pushSourceLineNumber(tuples);
2876 
2877 			Address elseblock = tuples.createAddress("elseblock");
2878 
2879 			getAst1().populateTuples(tuples);
2880 			tuples.ifFalse(elseblock);
2881 			if (getAst2() != null) {
2882 				getAst2().populateTuples(tuples);
2883 			}
2884 			if (getAst3() == null) {
2885 				tuples.address(elseblock);
2886 			} else {
2887 				Address end = tuples.createAddress("end");
2888 				tuples.gotoAddress(end);
2889 				tuples.address(elseblock);
2890 				getAst3().populateTuples(tuples);
2891 				tuples.address(end);
2892 			}
2893 			popSourceLineNumber(tuples);
2894 			return 0;
2895 		}
2896 	}
2897 
2898 	private final class TernaryExpressionAst extends ScalarExpressionAst {
2899 
2900 		private TernaryExpressionAst(AST a1, AST a2, AST a3) {
2901 			super(a1, a2, a3);
2902 		}
2903 
2904 		@Override
2905 		public int populateTuples(AwkTuples tuples) {
2906 			pushSourceLineNumber(tuples);
2907 
2908 			Address elseexpr = tuples.createAddress("elseexpr");
2909 			Address endTertiary = tuples.createAddress("endTertiary");
2910 
2911 			getAst1().populateTuples(tuples);
2912 			tuples.ifFalse(elseexpr);
2913 			getAst2().populateTuples(tuples);
2914 			tuples.gotoAddress(endTertiary);
2915 
2916 			tuples.address(elseexpr);
2917 			getAst3().populateTuples(tuples);
2918 			tuples.address(endTertiary);
2919 
2920 			popSourceLineNumber(tuples);
2921 			return 1;
2922 		}
2923 	}
2924 
2925 	private final class WhileStatementAst extends AST {
2926 
2927 		private Address breakAddress;
2928 		private Address continueAddress;
2929 
2930 		private WhileStatementAst(AST expr, AST block) {
2931 			super(expr, block);
2932 			addFlag(AstFlag.BREAKABLE);
2933 			addFlag(AstFlag.CONTINUEABLE);
2934 		}
2935 
2936 		@Override
2937 		public Address breakAddress() {
2938 			return breakAddress;
2939 		}
2940 
2941 		@Override
2942 		public Address continueAddress() {
2943 			return continueAddress;
2944 		}
2945 
2946 		@Override
2947 		public int populateTuples(AwkTuples tuples) {
2948 			pushSourceLineNumber(tuples);
2949 
2950 			breakAddress = tuples.createAddress("breakAddress");
2951 
2952 			// LOOP
2953 			Address loop = tuples.createAddress("loop");
2954 			tuples.address(loop);
2955 
2956 			// for while statements, the start-of-loop is the continue jump address
2957 			continueAddress = loop;
2958 
2959 			// condition
2960 			getAst1().populateTuples(tuples);
2961 			tuples.ifFalse(breakAddress);
2962 
2963 			if (getAst2() != null) {
2964 				getAst2().populateTuples(tuples);
2965 			}
2966 
2967 			tuples.gotoAddress(loop);
2968 
2969 			tuples.address(breakAddress);
2970 
2971 			popSourceLineNumber(tuples);
2972 			return 0;
2973 		}
2974 	}
2975 
2976 	private final class DoStatementAst extends AST {
2977 
2978 		private Address breakAddress;
2979 		private Address continueAddress;
2980 
2981 		private DoStatementAst(AST block, AST expr) {
2982 			super(block, expr);
2983 			addFlag(AstFlag.BREAKABLE);
2984 			addFlag(AstFlag.CONTINUEABLE);
2985 		}
2986 
2987 		@Override
2988 		public Address breakAddress() {
2989 			return breakAddress;
2990 		}
2991 
2992 		@Override
2993 		public Address continueAddress() {
2994 			return continueAddress;
2995 		}
2996 
2997 		@Override
2998 		public int populateTuples(AwkTuples tuples) {
2999 			pushSourceLineNumber(tuples);
3000 
3001 			breakAddress = tuples.createAddress("breakAddress");
3002 			continueAddress = tuples.createAddress("continueAddress");
3003 
3004 			// LOOP
3005 			Address loop = tuples.createAddress("loop");
3006 			tuples.address(loop);
3007 
3008 			if (getAst1() != null) {
3009 				getAst1().populateTuples(tuples);
3010 			}
3011 
3012 			// for do-while statements, the continue jump address is the loop condition
3013 			tuples.address(continueAddress);
3014 
3015 			// condition
3016 			getAst2().populateTuples(tuples);
3017 			tuples.ifTrue(loop);
3018 
3019 			// tuples.gotoAddress(loop);
3020 
3021 			tuples.address(breakAddress);
3022 
3023 			popSourceLineNumber(tuples);
3024 			return 0;
3025 		}
3026 	}
3027 
3028 	private final class ForStatementAst extends AST {
3029 
3030 		private Address breakAddress;
3031 		private Address continueAddress;
3032 
3033 		private ForStatementAst(AST expr1, AST expr2, AST expr3, AST block) {
3034 			super(expr1, expr2, expr3, block);
3035 			addFlag(AstFlag.BREAKABLE);
3036 			addFlag(AstFlag.CONTINUEABLE);
3037 		}
3038 
3039 		@Override
3040 		public Address breakAddress() {
3041 			return breakAddress;
3042 		}
3043 
3044 		@Override
3045 		public Address continueAddress() {
3046 			return continueAddress;
3047 		}
3048 
3049 		@Override
3050 		public int populateTuples(AwkTuples tuples) {
3051 			pushSourceLineNumber(tuples);
3052 
3053 			breakAddress = tuples.createAddress("breakAddress");
3054 			continueAddress = tuples.createAddress("continueAddress");
3055 
3056 			// initial actions
3057 			if (getAst1() != null) {
3058 				int ast1Result = getAst1().populateTuples(tuples);
3059 				for (int i = 0; i < ast1Result; i++) {
3060 					tuples.pop();
3061 				}
3062 			}
3063 			// LOOP
3064 			Address loop = tuples.createAddress("loop");
3065 			tuples.address(loop);
3066 
3067 			if (getAst2() != null) {
3068 				// condition
3069 				// assert(getAst2() != null);
3070 				getAst2().populateTuples(tuples);
3071 				tuples.ifFalse(breakAddress);
3072 			}
3073 
3074 			if (getAst4() != null) {
3075 				// post loop action
3076 				getAst4().populateTuples(tuples);
3077 			}
3078 
3079 			// for for-loops, the continue jump address is the post-loop-action
3080 			tuples.address(continueAddress);
3081 
3082 			// post-loop action
3083 			if (getAst3() != null) {
3084 				int ast3Result = getAst3().populateTuples(tuples);
3085 				for (int i = 0; i < ast3Result; i++) {
3086 					tuples.pop();
3087 				}
3088 			}
3089 
3090 			tuples.gotoAddress(loop);
3091 
3092 			tuples.address(breakAddress);
3093 
3094 			popSourceLineNumber(tuples);
3095 			return 0;
3096 		}
3097 	}
3098 
3099 	private final class ForInStatementAst extends AST {
3100 
3101 		private Address breakAddress;
3102 		private Address continueAddress;
3103 
3104 		private ForInStatementAst(AST keyIdAst, AST arrayIdAst, AST block) {
3105 			super(keyIdAst, arrayIdAst, block);
3106 			addFlag(AstFlag.BREAKABLE);
3107 			addFlag(AstFlag.CONTINUEABLE);
3108 		}
3109 
3110 		@Override
3111 		public Address breakAddress() {
3112 			return breakAddress;
3113 		}
3114 
3115 		@Override
3116 		public Address continueAddress() {
3117 			return continueAddress;
3118 		}
3119 
3120 		@Override
3121 		public int populateTuples(AwkTuples tuples) {
3122 			pushSourceLineNumber(tuples);
3123 
3124 			breakAddress = tuples.createAddress("breakAddress");
3125 
3126 			populateArrayOperandTuples(getAst2(), tuples, false, getAst2() + " is not an array");
3127 			// pops the array and pushes the keyset
3128 			tuples.keylist();
3129 
3130 			// stack now contains:
3131 			// keylist
3132 
3133 			// LOOP
3134 			Address loop = tuples.createAddress("loop");
3135 			tuples.address(loop);
3136 
3137 			// for for-in loops, the continue jump address is the start-of-loop address
3138 			continueAddress = loop;
3139 
3140 			// condition
3141 			tuples.dup();
3142 			tuples.isEmptyList(breakAddress);
3143 
3144 			// take an element off the set
3145 			tuples.dup();
3146 			tuples.getFirstAndRemoveFromList();
3147 			// assign it to the id
3148 			tuples.assign(((IDAst) getAst1()).offset, ((IDAst) getAst1()).isGlobal);
3149 			tuples.pop(); // remove the assignment result
3150 
3151 			if (getAst3() != null) {
3152 				// execute the block
3153 				getAst3().populateTuples(tuples);
3154 			}
3155 			// otherwise, there is no block to execute
3156 
3157 			tuples.gotoAddress(loop);
3158 
3159 			tuples.address(breakAddress);
3160 			tuples.pop(); // keylist
3161 
3162 			popSourceLineNumber(tuples);
3163 			return 0;
3164 		}
3165 	}
3166 
3167 	@SuppressWarnings("unused")
3168 	private final class EmptyStatementAst extends AST {
3169 
3170 		private EmptyStatementAst() {
3171 			super();
3172 		}
3173 
3174 		@Override
3175 		public int populateTuples(AwkTuples tuples) {
3176 			pushSourceLineNumber(tuples);
3177 			// nothing to populate!
3178 			popSourceLineNumber(tuples);
3179 			return 0;
3180 		}
3181 	}
3182 
3183 	/**
3184 	 * The AST for an expression used as a statement.
3185 	 * If the expression returns a value, the value is popped
3186 	 * off the stack and discarded.
3187 	 */
3188 	private final class ExpressionStatementAst extends AST {
3189 
3190 		private ExpressionStatementAst(AST expr) {
3191 			super(expr);
3192 		}
3193 
3194 		@Override
3195 		public int populateTuples(AwkTuples tuples) {
3196 			pushSourceLineNumber(tuples);
3197 			int exprCount = getAst1().populateTuples(tuples);
3198 			if (exprCount == 1) {
3199 				tuples.pop();
3200 			}
3201 			popSourceLineNumber(tuples);
3202 			return 0;
3203 		}
3204 	}
3205 
3206 	private final class AssignmentExpressionAst extends ScalarExpressionAst {
3207 
3208 		/** operand / operator */
3209 		private Token op;
3210 		private String text;
3211 
3212 		private AssignmentExpressionAst(AST lhs, Token op, String text, AST rhs) {
3213 			super(lhs, rhs);
3214 			this.op = op;
3215 			this.text = text;
3216 		}
3217 
3218 		@Override
3219 		public String toString() {
3220 			return super.toString() + " (" + op + "/" + text + ")";
3221 		}
3222 
3223 		@Override
3224 		public int populateTuples(AwkTuples tuples) {
3225 			pushSourceLineNumber(tuples);
3226 			getAst2().populateTuples(tuples); // here, stack contains one value
3227 			if (getAst1() instanceof IDAst) {
3228 				IDAst idAst = (IDAst) getAst1();
3229 				if (idAst.isArray()) {
3230 					throw new SemanticException("Cannot use " + idAst + " as a scalar. It is an array.");
3231 				}
3232 				idAst.setScalar(true);
3233 				boolean isSpecial = SPECIAL_VAR_NAMES.containsKey(idAst.id)
3234 						&& !"ENVIRON".equals(idAst.id)
3235 						&& !"ARGV".equals(idAst.id);
3236 				if (isSpecial) {
3237 					// value is on stack from RHS
3238 					switch (op) {
3239 					case EQUALS:
3240 						assignSpecial(tuples, idAst.id);
3241 						break;
3242 					case PLUS_EQ:
3243 						pushSpecialThenSwap(tuples, idAst.id);
3244 						tuples.add();
3245 						assignSpecial(tuples, idAst.id);
3246 						break;
3247 					case MINUS_EQ:
3248 						pushSpecialThenSwap(tuples, idAst.id);
3249 						tuples.subtract();
3250 						assignSpecial(tuples, idAst.id);
3251 						break;
3252 					case MULT_EQ:
3253 						pushSpecialThenSwap(tuples, idAst.id);
3254 						tuples.multiply();
3255 						assignSpecial(tuples, idAst.id);
3256 						break;
3257 					case DIV_EQ:
3258 						pushSpecialThenSwap(tuples, idAst.id);
3259 						tuples.divide();
3260 						assignSpecial(tuples, idAst.id);
3261 						break;
3262 					case MOD_EQ:
3263 						pushSpecialThenSwap(tuples, idAst.id);
3264 						tuples.mod();
3265 						assignSpecial(tuples, idAst.id);
3266 						break;
3267 					case POW_EQ:
3268 						pushSpecialThenSwap(tuples, idAst.id);
3269 						tuples.pow();
3270 						assignSpecial(tuples, idAst.id);
3271 						break;
3272 					default:
3273 						throw new Error("Unhandled op: " + op + " / " + text);
3274 					}
3275 					if ("RS".equals(idAst.id)) {
3276 						tuples.applyRS();
3277 					}
3278 				} else {
3279 					if (op == Token.EQUALS) {
3280 						// Expected side effect:
3281 						// Upon assignment, if the var is RS, reapply RS to input streams.
3282 						tuples.assign(idAst.offset, idAst.isGlobal);
3283 					} else if (op == Token.PLUS_EQ) {
3284 						tuples.plusEq(idAst.offset, idAst.isGlobal);
3285 					} else if (op == Token.MINUS_EQ) {
3286 						tuples.minusEq(idAst.offset, idAst.isGlobal);
3287 					} else if (op == Token.MULT_EQ) {
3288 						tuples.multEq(idAst.offset, idAst.isGlobal);
3289 					} else if (op == Token.DIV_EQ) {
3290 						tuples.divEq(idAst.offset, idAst.isGlobal);
3291 					} else if (op == Token.MOD_EQ) {
3292 						tuples.modEq(idAst.offset, idAst.isGlobal);
3293 					} else if (op == Token.POW_EQ) {
3294 						tuples.powEq(idAst.offset, idAst.isGlobal);
3295 					} else {
3296 						throw new Error("Unhandled op: " + op + " / " + text);
3297 					}
3298 					if (idAst.id.equals("RS")) {
3299 						tuples.applyRS();
3300 					}
3301 				}
3302 			} else if (getAst1() instanceof ArrayReferenceAst) {
3303 				ArrayReferenceAst arr = (ArrayReferenceAst) getAst1();
3304 				if (arr.getAst1() instanceof IDAst) {
3305 					IDAst idAst = (IDAst) arr.getAst1();
3306 					if (idAst.isScalar()) {
3307 						throw new SemanticException("Cannot use " + idAst + " as an array. It is a scalar.");
3308 					}
3309 					idAst.setArray(true);
3310 				}
3311 				arr.populateTargetReferenceTuples(tuples);
3312 				if (op == Token.EQUALS) {
3313 					tuples.assignMapElement();
3314 				} else if (op == Token.PLUS_EQ) {
3315 					tuples.plusEqMapElement();
3316 				} else if (op == Token.MINUS_EQ) {
3317 					tuples.minusEqMapElement();
3318 				} else if (op == Token.MULT_EQ) {
3319 					tuples.multEqMapElement();
3320 				} else if (op == Token.DIV_EQ) {
3321 					tuples.divEqMapElement();
3322 				} else if (op == Token.MOD_EQ) {
3323 					tuples.modEqMapElement();
3324 				} else if (op == Token.POW_EQ) {
3325 					tuples.powEqMapElement();
3326 				} else {
3327 					throw new NotImplementedError("Unhandled op: " + op + " / " + text + " for arrays.");
3328 				}
3329 			} else if (getAst1() instanceof DollarExpressionAst) {
3330 				DollarExpressionAst dollarExpr = (DollarExpressionAst) getAst1();
3331 				dollarExpr.getAst1().populateTuples(tuples); // stack contains eval of dollar arg
3332 
3333 				if (op == Token.EQUALS) {
3334 					tuples.assignAsInputField();
3335 				} else if (op == Token.PLUS_EQ) {
3336 					tuples.plusEqInputField();
3337 				} else if (op == Token.MINUS_EQ) {
3338 					tuples.minusEqInputField();
3339 				} else if (op == Token.MULT_EQ) {
3340 					tuples.multEqInputField();
3341 				} else if (op == Token.DIV_EQ) {
3342 					tuples.divEqInputField();
3343 				} else if (op == Token.MOD_EQ) {
3344 					tuples.modEqInputField();
3345 				} else if (op == Token.POW_EQ) {
3346 					tuples.powEqInputField();
3347 				} else {
3348 					throw new NotImplementedError("Unhandled op: " + op + " / " + text + " for dollar expressions.");
3349 				}
3350 			} else {
3351 				throw new SemanticException("Cannot perform an assignment on: " + getAst1());
3352 			}
3353 			popSourceLineNumber(tuples);
3354 			return 1;
3355 		}
3356 
3357 		private void pushSpecialThenSwap(AwkTuples tuples, String id) {
3358 			switch (id) {
3359 			case "NF":
3360 				tuples.pushNF();
3361 				break;
3362 			case "NR":
3363 				tuples.pushNR();
3364 				break;
3365 			case "FNR":
3366 				tuples.pushFNR();
3367 				break;
3368 			case "FS":
3369 				tuples.pushFS();
3370 				break;
3371 			case "RS":
3372 				tuples.pushRS();
3373 				break;
3374 			case "OFS":
3375 				tuples.pushOFS();
3376 				break;
3377 			case "ORS":
3378 				tuples.pushORS();
3379 				break;
3380 			case "RSTART":
3381 				tuples.pushRSTART();
3382 				break;
3383 			case "RLENGTH":
3384 				tuples.pushRLENGTH();
3385 				break;
3386 			case "FILENAME":
3387 				tuples.pushFILENAME();
3388 				break;
3389 			case "SUBSEP":
3390 				tuples.pushSUBSEP();
3391 				break;
3392 			case "CONVFMT":
3393 				tuples.pushCONVFMT();
3394 				break;
3395 			case "OFMT":
3396 				tuples.pushOFMT();
3397 				break;
3398 			case "ARGC":
3399 				tuples.pushARGC();
3400 				break;
3401 			default:
3402 				throw new Error("Unhandled special var: " + id);
3403 			}
3404 			tuples.swap();
3405 		}
3406 
3407 		private void assignSpecial(AwkTuples tuples, String id) {
3408 			switch (id) {
3409 			case "NF":
3410 				tuples.assignNF();
3411 				break;
3412 			case "NR":
3413 				tuples.assignNR();
3414 				break;
3415 			case "FNR":
3416 				tuples.assignFNR();
3417 				break;
3418 			case "FS":
3419 				tuples.assignFS();
3420 				break;
3421 			case "RS":
3422 				tuples.assignRS();
3423 				break;
3424 			case "OFS":
3425 				tuples.assignOFS();
3426 				break;
3427 			case "ORS":
3428 				tuples.assignORS();
3429 				break;
3430 			case "RSTART":
3431 				tuples.assignRSTART();
3432 				break;
3433 			case "RLENGTH":
3434 				tuples.assignRLENGTH();
3435 				break;
3436 			case "FILENAME":
3437 				tuples.assignFILENAME();
3438 				break;
3439 			case "SUBSEP":
3440 				tuples.assignSUBSEP();
3441 				break;
3442 			case "CONVFMT":
3443 				tuples.assignCONVFMT();
3444 				break;
3445 			case "OFMT":
3446 				tuples.assignOFMT();
3447 				break;
3448 			case "ARGC":
3449 				tuples.assignARGC();
3450 				break;
3451 			default:
3452 				throw new Error("Unhandled special var: " + id);
3453 			}
3454 		}
3455 	}
3456 
3457 	private final class InExpressionAst extends ScalarExpressionAst {
3458 
3459 		private InExpressionAst(AST arg, AST arr) {
3460 			super(arg, arr);
3461 		}
3462 
3463 		@Override
3464 		public int populateTuples(AwkTuples tuples) {
3465 			pushSourceLineNumber(tuples);
3466 			if (!(getAst2() instanceof IDAst) && !(getAst2() instanceof ArrayReferenceAst)) {
3467 				throw new SemanticException("Expecting an array for rhs of IN. Got an expression.");
3468 			}
3469 
3470 			getAst1().populateTuples(tuples);
3471 			populateArrayOperandTuples(getAst2(), tuples, false, "Expecting an array for rhs of IN. Got a scalar.");
3472 			tuples.isIn();
3473 
3474 			popSourceLineNumber(tuples);
3475 			return 1;
3476 		}
3477 	}
3478 
3479 	private final class ComparisonExpressionAst extends ScalarExpressionAst {
3480 
3481 		/**
3482 		 * operand / operator
3483 		 */
3484 		private Token op;
3485 		private String text;
3486 
3487 		private ComparisonExpressionAst(AST lhs, Token op, String text, AST rhs) {
3488 			super(lhs, rhs);
3489 			this.op = op;
3490 			this.text = text;
3491 		}
3492 
3493 		@Override
3494 		public String toString() {
3495 			return super.toString() + " (" + op + "/" + text + ")";
3496 		}
3497 
3498 		@Override
3499 		public int populateTuples(AwkTuples tuples) {
3500 			pushSourceLineNumber(tuples);
3501 
3502 			getAst1().populateTuples(tuples);
3503 			getAst2().populateTuples(tuples);
3504 			// 2 values on the stack
3505 
3506 			if (op == Token.EQ) {
3507 				tuples.cmpEq();
3508 			} else if (op == Token.NE) {
3509 				tuples.cmpEq();
3510 				tuples.not();
3511 			} else if (op == Token.LT) {
3512 				tuples.cmpLt();
3513 			} else if (op == Token.GT) {
3514 				tuples.cmpGt();
3515 			} else if (op == Token.LE) {
3516 				tuples.cmpGt();
3517 				tuples.not();
3518 			} else if (op == Token.GE) {
3519 				tuples.cmpLt();
3520 				tuples.not();
3521 			} else if (op == Token.MATCHES) {
3522 				tuples.matches();
3523 			} else if (op == Token.NOT_MATCHES) {
3524 				tuples.matches();
3525 				tuples.not();
3526 			} else {
3527 				throw new Error("Unhandled op: " + op + " / " + text);
3528 			}
3529 
3530 			popSourceLineNumber(tuples);
3531 			return 1;
3532 		}
3533 	}
3534 
3535 	private final class LogicalExpressionAst extends ScalarExpressionAst {
3536 
3537 		/**
3538 		 * operand / operator
3539 		 */
3540 		private Token op;
3541 		private String text;
3542 
3543 		private LogicalExpressionAst(AST lhs, Token op, String text, AST rhs) {
3544 			super(lhs, rhs);
3545 			this.op = op;
3546 			this.text = text;
3547 		}
3548 
3549 		@Override
3550 		public String toString() {
3551 			return super.toString() + " (" + op + "/" + text + ")";
3552 		}
3553 
3554 		@Override
3555 		public int populateTuples(AwkTuples tuples) {
3556 			pushSourceLineNumber(tuples);
3557 			// exhibit short-circuit behavior
3558 			Address end = tuples.createAddress("end");
3559 			getAst1().populateTuples(tuples);
3560 			tuples.dup();
3561 			if (op == Token.OR) {
3562 				// shortCircuit when op is Token.OR and 1st arg is true
3563 				tuples.ifTrue(end);
3564 			} else if (op == Token.AND) {
3565 				tuples.ifFalse(end);
3566 			}
3567 			tuples.pop();
3568 			getAst2().populateTuples(tuples);
3569 			tuples.address(end);
3570 
3571 			// turn the result into boolean one or zero
3572 			tuples.toNumber();
3573 			popSourceLineNumber(tuples);
3574 			return 1;
3575 		}
3576 	}
3577 
3578 	private final class BinaryExpressionAst extends ScalarExpressionAst {
3579 
3580 		/**
3581 		 * operand / operator
3582 		 */
3583 		private Token op;
3584 		private String text;
3585 
3586 		private BinaryExpressionAst(AST lhs, Token op, String text, AST rhs) {
3587 			super(lhs, rhs);
3588 			this.op = op;
3589 			this.text = text;
3590 		}
3591 
3592 		@Override
3593 		public String toString() {
3594 			return super.toString() + " (" + op + "/" + text + ")";
3595 		}
3596 
3597 		@Override
3598 		public int populateTuples(AwkTuples tuples) {
3599 			pushSourceLineNumber(tuples);
3600 			getAst1().populateTuples(tuples);
3601 			getAst2().populateTuples(tuples);
3602 			if (op == Token.PLUS) {
3603 				tuples.add();
3604 			} else if (op == Token.MINUS) {
3605 				tuples.subtract();
3606 			} else if (op == Token.MULT) {
3607 				tuples.multiply();
3608 			} else if (op == Token.DIVIDE) {
3609 				tuples.divide();
3610 			} else if (op == Token.MOD) {
3611 				tuples.mod();
3612 			} else if (op == Token.POW) {
3613 				tuples.pow();
3614 			} else {
3615 				throw new Error("Unhandled op: " + op + " / " + this);
3616 			}
3617 			popSourceLineNumber(tuples);
3618 			return 1;
3619 		}
3620 	}
3621 
3622 	private final class ConcatExpressionAst extends ScalarExpressionAst {
3623 
3624 		private ConcatExpressionAst(AST lhs, AST rhs) {
3625 			super(lhs, rhs);
3626 		}
3627 
3628 		@Override
3629 		public int populateTuples(AwkTuples tuples) {
3630 			pushSourceLineNumber(tuples);
3631 			getAst1().populateTuples(tuples);
3632 			getAst2().populateTuples(tuples);
3633 			tuples.concat();
3634 			popSourceLineNumber(tuples);
3635 			return 1;
3636 		}
3637 	}
3638 
3639 	private final class NegativeExpressionAst extends ScalarExpressionAst {
3640 
3641 		private NegativeExpressionAst(AST expr) {
3642 			super(expr);
3643 		}
3644 
3645 		@Override
3646 		public int populateTuples(AwkTuples tuples) {
3647 			pushSourceLineNumber(tuples);
3648 			getAst1().populateTuples(tuples);
3649 			tuples.negate();
3650 			popSourceLineNumber(tuples);
3651 			return 1;
3652 		}
3653 	}
3654 
3655 	private final class UnaryPlusExpressionAst extends ScalarExpressionAst {
3656 
3657 		private UnaryPlusExpressionAst(AST expr) {
3658 			super(expr);
3659 		}
3660 
3661 		@Override
3662 		public int populateTuples(AwkTuples tuples) {
3663 			pushSourceLineNumber(tuples);
3664 			getAst1().populateTuples(tuples);
3665 			tuples.unaryPlus();
3666 			popSourceLineNumber(tuples);
3667 			return 1;
3668 		}
3669 	}
3670 
3671 	private final class NotExpressionAst extends ScalarExpressionAst {
3672 
3673 		private NotExpressionAst(AST expr) {
3674 			super(expr);
3675 		}
3676 
3677 		@Override
3678 		public int populateTuples(AwkTuples tuples) {
3679 			pushSourceLineNumber(tuples);
3680 			getAst1().populateTuples(tuples);
3681 			tuples.not();
3682 			popSourceLineNumber(tuples);
3683 			return 1;
3684 		}
3685 	}
3686 
3687 	private final class DollarExpressionAst extends ScalarExpressionAst {
3688 
3689 		private DollarExpressionAst(AST expr) {
3690 			super(expr);
3691 		}
3692 
3693 		@Override
3694 		public int populateTuples(AwkTuples tuples) {
3695 			pushSourceLineNumber(tuples);
3696 			getAst1().populateTuples(tuples);
3697 			tuples.getInputField();
3698 			popSourceLineNumber(tuples);
3699 			return 1;
3700 		}
3701 	}
3702 
3703 	private final class ArrayIndexAst extends ScalarExpressionAst {
3704 
3705 		private ArrayIndexAst(AST exprAst, AST next) {
3706 			super(exprAst, next);
3707 		}
3708 
3709 		@Override
3710 		public int populateTuples(AwkTuples tuples) {
3711 			pushSourceLineNumber(tuples);
3712 			AST ptr = this;
3713 			int cnt = 0;
3714 			while (ptr != null) {
3715 				ptr.getAst1().populateTuples(tuples);
3716 				++cnt;
3717 				ptr = ptr.getAst2();
3718 			}
3719 			if (cnt > 1) {
3720 				tuples.applySubsep(cnt);
3721 			}
3722 			popSourceLineNumber(tuples);
3723 			return 1;
3724 		}
3725 	}
3726 
3727 	// made classname all capitals to stand out in a syntax tree dump
3728 	private final class StatementListAst extends AST {
3729 
3730 		private StatementListAst(AST statementAst, AST rest) {
3731 			super(statementAst, rest);
3732 		}
3733 
3734 		/**
3735 		 * Recursively process statements within this statement list.
3736 		 * <p>
3737 		 * It originally was done linearly. However, quirks in the grammar required
3738 		 * a more general, recursive approach to processing this "list".
3739 		 * <p>
3740 		 * Note: this should be reevaluated periodically in case the grammar
3741 		 * becomes linear again.
3742 		 */
3743 		@Override
3744 		public int populateTuples(AwkTuples tuples) {
3745 			pushSourceLineNumber(tuples);
3746 			// typical recursive processing of a list
3747 			getAst1().populateTuples(tuples);
3748 			if (getAst2() != null) {
3749 				getAst2().populateTuples(tuples);
3750 			}
3751 			popSourceLineNumber(tuples);
3752 			return 0;
3753 		}
3754 
3755 		@Override
3756 		public String toString() {
3757 			return super.toString() + " <" + getAst1() + ">";
3758 		}
3759 	}
3760 
3761 	// made non-static to access the symbol table
3762 	private final class FunctionDefAst extends AST {
3763 
3764 		private String id;
3765 		private Address functionAddress;
3766 		private Address returnAddress;
3767 
3768 		@Override
3769 		public Address returnAddress() {
3770 			return returnAddress;
3771 		}
3772 
3773 		private FunctionDefAst(String id, AST params, AST funcBody) {
3774 			super(params, funcBody);
3775 			this.id = id;
3776 			setFunctionFlag(true);
3777 			addFlag(AstFlag.RETURNABLE);
3778 		}
3779 
3780 		public Address getAddress() {
3781 			return functionAddress;
3782 		}
3783 
3784 		@Override
3785 		public int populateTuples(AwkTuples tuples) {
3786 			pushSourceLineNumber(tuples);
3787 
3788 			functionAddress = tuples.createAddress("function: " + id);
3789 			returnAddress = tuples.createAddress("returnAddress for " + id);
3790 
3791 			// annotate the tuple list
3792 			// (useful for compilation,
3793 			// not necessary for interpretation)
3794 			tuples.function(id, paramCount());
3795 
3796 			// functionAddress refers to first function body statement
3797 			// rather than to function def opcode because during
3798 			// interpretation, the function definition is a nop,
3799 			// and for compilation, the next match of the function
3800 			// name can be used
3801 			tuples.address(functionAddress);
3802 
3803 			// the stack contains the parameters to the function call (in rev order, which is good)
3804 
3805 			// execute the body
3806 			// (function body could be empty [no statements])
3807 			if (getAst2() != null) {
3808 				getAst2().populateTuples(tuples);
3809 			}
3810 
3811 			tuples.address(returnAddress);
3812 
3813 			tuples.returnFromFunction();
3814 
3815 			/////////////////////////////////////////////
3816 
3817 			popSourceLineNumber(tuples);
3818 			return 0;
3819 		}
3820 
3821 		int paramCount() {
3822 			AST ptr = getAst1();
3823 			int count = 0;
3824 			while (ptr != null) {
3825 				++count;
3826 				ptr = ptr.getAst1();
3827 			}
3828 			return count;
3829 		}
3830 
3831 		void checkActualToFormalParameters(AST actualParamList) {
3832 			AST aPtr = actualParamList;
3833 			FunctionDefParamListAst fPtr = (FunctionDefParamListAst) getAst1();
3834 			while (aPtr != null) {
3835 				// actual parameter
3836 				AST aparam = aPtr.getAst1();
3837 				// formal function parameter
3838 				AST fparam = symbolTable.getFunctionParameterIDAST(id, fPtr.id);
3839 
3840 				if (fparam.isArray()) {
3841 					if (aparam instanceof IDAst) {
3842 						IDAst aparamIdAst = (IDAst) aparam;
3843 						if (aparamIdAst.isScalar()) {
3844 							aparam
3845 									.throwSemanticException(
3846 											id + ": Actual parameter (" + aparam
3847 													+ ") is a scalar, but formal parameter is used like an array.");
3848 						}
3849 						aparamIdAst.setArray(true);
3850 					} else if (!(aparam instanceof ArrayReferenceAst)) {
3851 						aparam
3852 								.throwSemanticException(
3853 										id + ": Actual parameter (" + aparam + ") is not an array or subarray reference.");
3854 					}
3855 				} else if (fparam.isScalar() && aparam instanceof IDAst) {
3856 					IDAst aparamIdAst = (IDAst) aparam;
3857 					if (aparamIdAst.isArray()) {
3858 						aparam
3859 								.throwSemanticException(
3860 										id + ": Actual parameter (" + aparam
3861 												+ ") is an array, but formal parameter is used like a scalar.");
3862 					}
3863 					aparamIdAst.setScalar(true);
3864 				}
3865 				// next
3866 				aPtr = aPtr.getAst2();
3867 				fPtr = (FunctionDefParamListAst) fPtr.getAst1();
3868 			}
3869 		}
3870 	}
3871 
3872 	private final class FunctionCallAst extends ScalarExpressionAst {
3873 
3874 		private FunctionProxy functionProxy;
3875 
3876 		private FunctionCallAst(FunctionProxy functionProxy, AST params) {
3877 			super(params);
3878 			this.functionProxy = functionProxy;
3879 		}
3880 
3881 		/**
3882 		 * Applies several semantic checks with respect
3883 		 * to user-defined-function calls.
3884 		 * <p>
3885 		 * The checks performed are:
3886 		 * <ul>
3887 		 * <li>Make sure the function is defined.
3888 		 * <li>The number of actual parameters does not
3889 		 * exceed the number of formal parameters.
3890 		 * <li>Matches actual parameters to formal parameter
3891 		 * usage with respect to whether they are
3892 		 * scalars, arrays, or either.
3893 		 * (This determination is based on how
3894 		 * the formal parameters are used within
3895 		 * the function block.)
3896 		 * </ul>
3897 		 * A failure of any one of these checks
3898 		 * results in a SemanticException.
3899 		 *
3900 		 * @throws SemanticException upon a failure of
3901 		 *         any of the semantic checks specified above.
3902 		 */
3903 		@Override
3904 		public void semanticAnalysis() throws SemanticException {
3905 			if (!functionProxy.isDefined()) {
3906 				throw new SemanticException("function " + functionProxy + " not defined");
3907 			}
3908 			int actualParamCountLocal;
3909 			if (getAst1() == null) {
3910 				actualParamCountLocal = 0;
3911 			} else {
3912 				actualParamCountLocal = actualParamCount();
3913 			}
3914 			int formalParamCount = functionProxy.getFunctionParamCount();
3915 			if (formalParamCount < actualParamCountLocal) {
3916 				throw new SemanticException(
3917 						"the "
3918 								+ functionProxy.getFunctionName()
3919 								+ " function"
3920 								+ " only accepts at most "
3921 								+ formalParamCount
3922 								+ " parameter(s), not "
3923 								+ actualParamCountLocal);
3924 			}
3925 			if (getAst1() != null) {
3926 				functionProxy.checkActualToFormalParameters(getAst1());
3927 			}
3928 		}
3929 
3930 		@Override
3931 		public int populateTuples(AwkTuples tuples) {
3932 			pushSourceLineNumber(tuples);
3933 			if (!functionProxy.isDefined()) {
3934 				throw new SemanticException("function " + functionProxy + " not defined");
3935 			}
3936 			tuples.scriptThis();
3937 			int actualParamCountLocal;
3938 			if (getAst1() == null) {
3939 				actualParamCountLocal = 0;
3940 			} else {
3941 				actualParamCountLocal = populateActualParameters(
3942 						tuples,
3943 						(FunctionCallParamListAst) getAst1(),
3944 						collectArrayParameterIndexes(functionProxy.functionDefAst),
3945 						0);
3946 			}
3947 			int formalParamCount = functionProxy.getFunctionParamCount();
3948 			if (formalParamCount < actualParamCountLocal) {
3949 				throw new SemanticException(
3950 						"the "
3951 								+ functionProxy.getFunctionName()
3952 								+ " function"
3953 								+ " only accepts at most "
3954 								+ formalParamCount
3955 								+ " parameter(s), not "
3956 								+ actualParamCountLocal);
3957 			}
3958 
3959 			functionProxy.checkActualToFormalParameters(getAst1());
3960 			tuples.callFunction(functionProxy, functionProxy.getFunctionName(), formalParamCount, actualParamCountLocal);
3961 			popSourceLineNumber(tuples);
3962 			return 1;
3963 		}
3964 
3965 		private int actualParamCount() {
3966 			int cnt = 0;
3967 			AST ptr = getAst1();
3968 			while (ptr != null) {
3969 				++cnt;
3970 				ptr = ptr.getAst2();
3971 			}
3972 			return cnt;
3973 		}
3974 	}
3975 
3976 	private final class BuiltinFunctionCallAst extends ScalarExpressionAst {
3977 
3978 		private String id;
3979 		private int fIdx;
3980 
3981 		private BuiltinFunctionCallAst(String id, AST params) {
3982 			super(params);
3983 			this.id = id;
3984 			this.fIdx = BUILTIN_FUNC_NAMES.get(id);
3985 		}
3986 
3987 		@Override
3988 		public int populateTuples(AwkTuples tuples) {
3989 			pushSourceLineNumber(tuples);
3990 			if (fIdx == BUILTIN_FUNC_NAMES.get("sprintf")) {
3991 				if (getAst1() == null) {
3992 					throw new SemanticException("sprintf requires at least 1 argument");
3993 				}
3994 				int ast1Result = getAst1().populateTuples(tuples);
3995 				if (ast1Result == 0) {
3996 					throw new SemanticException("sprintf requires at minimum 1 argument");
3997 				}
3998 				tuples.sprintf(ast1Result);
3999 				popSourceLineNumber(tuples);
4000 				return 1;
4001 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("close")) {
4002 				if (getAst1() == null) {
4003 					throw new SemanticException("close requires 1 argument");
4004 				}
4005 				int ast1Result = getAst1().populateTuples(tuples);
4006 				if (ast1Result != 1) {
4007 					throw new SemanticException("close requires only 1 argument");
4008 				}
4009 				tuples.close();
4010 				popSourceLineNumber(tuples);
4011 				return 1;
4012 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("length")) {
4013 				if (getAst1() == null) {
4014 					tuples.length(0);
4015 				} else {
4016 					int ast1Result = getAst1().populateTuples(tuples);
4017 					if (ast1Result != 1) {
4018 						throw new SemanticException("length requires at least one argument");
4019 					}
4020 					tuples.length(1);
4021 				}
4022 				popSourceLineNumber(tuples);
4023 				return 1;
4024 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("srand")) {
4025 				if (getAst1() == null) {
4026 					tuples.srand(0);
4027 				} else {
4028 					int ast1Result = getAst1().populateTuples(tuples);
4029 					if (ast1Result != 1) {
4030 						throw new SemanticException("srand takes either 0 or one argument, not " + ast1Result);
4031 					}
4032 					tuples.srand(1);
4033 				}
4034 				popSourceLineNumber(tuples);
4035 				return 1;
4036 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("rand")) {
4037 				if (getAst1() != null) {
4038 					throw new SemanticException("rand does not take arguments");
4039 				}
4040 				tuples.rand();
4041 				popSourceLineNumber(tuples);
4042 				return 1;
4043 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("sqrt")) {
4044 				int ast1Result = getAst1().populateTuples(tuples);
4045 				if (ast1Result != 1) {
4046 					throw new SemanticException("sqrt requires only 1 argument");
4047 				}
4048 				tuples.sqrt();
4049 				popSourceLineNumber(tuples);
4050 				return 1;
4051 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("int")) {
4052 				int ast1Result = getAst1().populateTuples(tuples);
4053 				if (ast1Result != 1) {
4054 					throw new SemanticException("int requires only 1 argument");
4055 				}
4056 				tuples.intFunc();
4057 				popSourceLineNumber(tuples);
4058 				return 1;
4059 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("log")) {
4060 				int ast1Result = getAst1().populateTuples(tuples);
4061 				if (ast1Result != 1) {
4062 					throw new SemanticException("log requires only 1 argument");
4063 				}
4064 				tuples.log();
4065 				popSourceLineNumber(tuples);
4066 				return 1;
4067 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("exp")) {
4068 				int ast1Result = getAst1().populateTuples(tuples);
4069 				if (ast1Result != 1) {
4070 					throw new SemanticException("exp requires only 1 argument");
4071 				}
4072 				tuples.exp();
4073 				popSourceLineNumber(tuples);
4074 				return 1;
4075 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("sin")) {
4076 				int ast1Result = getAst1().populateTuples(tuples);
4077 				if (ast1Result != 1) {
4078 					throw new SemanticException("sin requires only 1 argument");
4079 				}
4080 				tuples.sin();
4081 				popSourceLineNumber(tuples);
4082 				return 1;
4083 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("cos")) {
4084 				int ast1Result = getAst1().populateTuples(tuples);
4085 				if (ast1Result != 1) {
4086 					throw new SemanticException("cos requires only 1 argument");
4087 				}
4088 				tuples.cos();
4089 				popSourceLineNumber(tuples);
4090 				return 1;
4091 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("atan2")) {
4092 				int ast1Result = getAst1().populateTuples(tuples);
4093 				if (ast1Result != 2) {
4094 					throw new SemanticException("atan2 requires 2 arguments");
4095 				}
4096 				tuples.atan2();
4097 				popSourceLineNumber(tuples);
4098 				return 1;
4099 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("match")) {
4100 				int ast1Result = getAst1().populateTuples(tuples);
4101 				if (ast1Result != 2) {
4102 					throw new SemanticException("match requires 2 arguments");
4103 				}
4104 				tuples.match();
4105 				popSourceLineNumber(tuples);
4106 				return 1;
4107 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("index")) {
4108 				int ast1Result = getAst1().populateTuples(tuples);
4109 				if (ast1Result != 2) {
4110 					throw new SemanticException("index requires 2 arguments");
4111 				}
4112 				tuples.index();
4113 				popSourceLineNumber(tuples);
4114 				return 1;
4115 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("sub") || fIdx == BUILTIN_FUNC_NAMES.get("gsub")) {
4116 				if (getAst1() == null || getAst1().getAst2() == null || getAst1().getAst2().getAst1() == null) {
4117 					throw new SemanticException("sub needs at least 2 arguments");
4118 				}
4119 				boolean isGsub = fIdx == BUILTIN_FUNC_NAMES.get("gsub");
4120 				int numargs = 0;
4121 				for (AST paramPtr = getAst1(); paramPtr != null; paramPtr = paramPtr.getAst2()) {
4122 					numargs++;
4123 				}
4124 				if (numargs != 2 && numargs != 3) {
4125 					throw new SemanticException("sub requires 2 or 3 arguments, not " + numargs);
4126 				}
4127 
4128 				getAst1().getAst1().populateTuples(tuples);
4129 				getAst1().getAst2().getAst1().populateTuples(tuples);
4130 				if (numargs == 3) {
4131 					AST targetAst = getAst1().getAst2().getAst2().getAst1();
4132 					if (targetAst instanceof ArrayReferenceAst) {
4133 						((ArrayReferenceAst) targetAst).populateTargetValueTuples(tuples);
4134 					} else {
4135 						targetAst.populateTuples(tuples);
4136 					}
4137 				}
4138 
4139 				// stack contains arg1,arg2[,arg3] - in that pop() order
4140 
4141 				if (numargs == 2) {
4142 					tuples.subForDollar0(isGsub);
4143 				} else if (numargs == 3) {
4144 					AST ptr = getAst1().getAst2().getAst2().getAst1();
4145 					if (ptr instanceof IDAst) {
4146 						IDAst idAst = (IDAst) ptr;
4147 						if (idAst.isArray()) {
4148 							throw new SemanticException("sub cannot accept an unindexed array as its 3rd argument");
4149 						}
4150 						idAst.setScalar(true);
4151 						tuples.subForVariable(idAst.offset, idAst.isGlobal, isGsub);
4152 					} else if (ptr instanceof ArrayReferenceAst) {
4153 						ArrayReferenceAst arrAst = (ArrayReferenceAst) ptr;
4154 						if (arrAst.getAst1() instanceof IDAst) {
4155 							IDAst idAst = (IDAst) arrAst.getAst1();
4156 							if (idAst.isScalar()) {
4157 								throw new SemanticException("Cannot use " + idAst + " as an array.");
4158 							}
4159 							idAst.setArray(true);
4160 						}
4161 						arrAst.populateTargetReferenceTuples(tuples);
4162 						tuples.subForMapReference(isGsub);
4163 					} else if (ptr instanceof DollarExpressionAst) {
4164 						// push the field ref
4165 						DollarExpressionAst dollarExpr = (DollarExpressionAst) ptr;
4166 						dollarExpr.getAst1().populateTuples(tuples);
4167 						tuples.subForDollarReference(isGsub);
4168 					} else {
4169 						throw new SemanticException(
4170 								"sub's 3rd argument must be either an id, an array reference, or an input field reference");
4171 					}
4172 				}
4173 				popSourceLineNumber(tuples);
4174 				return 1;
4175 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("split")) {
4176 				// split can take 2 or 3 args:
4177 				// split (string, array [,fs])
4178 				// the 2nd argument is pass by reference, which is ok (?)
4179 
4180 				// funccallparamlist.funccallparamlist.idAst
4181 				if (getAst1() == null || getAst1().getAst2() == null || getAst1().getAst2().getAst1() == null) {
4182 					throw new SemanticException("split needs at least 2 arguments");
4183 				}
4184 				AST ptr = getAst1().getAst2().getAst1();
4185 				if (!(ptr instanceof IDAst) && !(ptr instanceof ArrayReferenceAst)) {
4186 					throw new SemanticException("split needs an array or subarray reference as its 2nd argument");
4187 				}
4188 				if (ptr instanceof IDAst) {
4189 					IDAst arrAst = (IDAst) ptr;
4190 					if (arrAst.isScalar()) {
4191 						throw new SemanticException("split's 2nd arg cannot be a scalar");
4192 					}
4193 					arrAst.setArray(true);
4194 				}
4195 
4196 				int ast1Result = 0;
4197 				for (AST paramPtr = getAst1(); paramPtr != null; paramPtr = paramPtr.getAst2()) {
4198 					ast1Result++;
4199 				}
4200 				if (ast1Result != 2 && ast1Result != 3) {
4201 					throw new SemanticException("split requires 2 or 3 arguments, not " + ast1Result);
4202 				}
4203 
4204 				getAst1().getAst1().populateTuples(tuples);
4205 				populateArrayOperandTuples(
4206 						ptr,
4207 						tuples,
4208 						true,
4209 						"split's 2nd arg must be an array or subarray reference");
4210 				if (ast1Result == 3) {
4211 					getAst1().getAst2().getAst2().getAst1().populateTuples(tuples);
4212 				}
4213 				tuples.split(ast1Result);
4214 				popSourceLineNumber(tuples);
4215 				return 1;
4216 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("substr")) {
4217 				if (getAst1() == null) {
4218 					throw new SemanticException("substr requires at least 2 arguments");
4219 				}
4220 				int ast1Result = getAst1().populateTuples(tuples);
4221 				if (ast1Result != 2 && ast1Result != 3) {
4222 					throw new SemanticException("substr requires 2 or 3 arguments, not " + ast1Result);
4223 				}
4224 				tuples.substr(ast1Result);
4225 				popSourceLineNumber(tuples);
4226 				return 1;
4227 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("tolower")) {
4228 				if (getAst1() == null) {
4229 					throw new SemanticException("tolower requires 1 argument");
4230 				}
4231 				int ast1Result = getAst1().populateTuples(tuples);
4232 				if (ast1Result != 1) {
4233 					throw new SemanticException("tolower requires only 1 argument");
4234 				}
4235 				tuples.tolower();
4236 				popSourceLineNumber(tuples);
4237 				return 1;
4238 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("toupper")) {
4239 				if (getAst1() == null) {
4240 					throw new SemanticException("toupper requires 1 argument");
4241 				}
4242 				int ast1Result = getAst1().populateTuples(tuples);
4243 				if (ast1Result != 1) {
4244 					throw new SemanticException("toupper requires only 1 argument");
4245 				}
4246 				tuples.toupper();
4247 				popSourceLineNumber(tuples);
4248 				return 1;
4249 			} else if (fIdx == BUILTIN_FUNC_NAMES.get("system")) {
4250 				if (getAst1() == null) {
4251 					throw new SemanticException("system requires 1 argument");
4252 				}
4253 				int ast1Result = getAst1().populateTuples(tuples);
4254 				if (ast1Result != 1) {
4255 					throw new SemanticException("system requires only 1 argument");
4256 				}
4257 				tuples.system();
4258 				popSourceLineNumber(tuples);
4259 				return 1;
4260 			} else {
4261 				throw new NotImplementedError("builtin: " + id);
4262 			}
4263 		}
4264 	}
4265 
4266 	private final class FunctionCallParamListAst extends AST {
4267 
4268 		private FunctionCallParamListAst(AST expr, AST rest) {
4269 			super(expr, rest);
4270 		}
4271 
4272 		@Override
4273 		public int populateTuples(AwkTuples tuples) {
4274 			pushSourceLineNumber(tuples);
4275 			int retval;
4276 			if (getAst2() == null) {
4277 				retval = getAst1().populateTuples(tuples);
4278 			} else {
4279 				retval = getAst1().populateTuples(tuples) + getAst2().populateTuples(tuples);
4280 			}
4281 			popSourceLineNumber(tuples);
4282 			return retval;
4283 		}
4284 	}
4285 
4286 	private final class FunctionDefParamListAst extends AST {
4287 
4288 		private String id;
4289 
4290 		private FunctionDefParamListAst(String id, AST rest) {
4291 			super(rest);
4292 			this.id = id;
4293 		}
4294 
4295 		public int populateTuples(AwkTuples tuples) {
4296 			throw new Error("Cannot 'execute' function definition parameter list (formal parameters) in this manner.");
4297 		}
4298 
4299 		/**
4300 		 * According to the spec
4301 		 * (http://www.opengroup.org/onlinepubs/007908799/xcu/awk.html)
4302 		 * formal function parameters cannot be special variables,
4303 		 * such as NF, NR, etc).
4304 		 *
4305 		 * @throws SemanticException upon a semantic error.
4306 		 */
4307 		@Override
4308 		public void semanticAnalysis() throws SemanticException {
4309 			// could do it recursively, but not necessary
4310 			// since all getAst1()'s are FunctionDefParamList's
4311 			// and, thus, terminals (no need to do further
4312 			// semantic analysis)
4313 
4314 			FunctionDefParamListAst ptr = this;
4315 			while (ptr != null) {
4316 				if (SPECIAL_VAR_NAMES.get(ptr.id) != null) {
4317 					throw new SemanticException("Special variable " + ptr.id + " cannot be used as a formal parameter");
4318 				}
4319 				ptr = (FunctionDefParamListAst) ptr.getAst1();
4320 			}
4321 		}
4322 	}
4323 
4324 	/**
4325 	 * Flag for non-statement expressions.
4326 	 * Unknown for certain, but I think this is done
4327 	 * to avoid partial variable assignment mistakes.
4328 	 * For example, instead of a=3, the programmer
4329 	 * inadvertently places the a on the line. If IDAsts
4330 	 * were not tagged with AstFlag.NON_STATEMENT, then the
4331 	 * incomplete assignment would parse properly, and
4332 	 * the developer might remain unaware of this issue.
4333 	 */
4334 
4335 	private final class IDAst extends AST {
4336 
4337 		private String id;
4338 		private int offset = AVM.NULL_OFFSET;
4339 		private boolean isGlobal;
4340 		private boolean referenced;
4341 
4342 		private IDAst(String id, boolean isGlobal) {
4343 			this.id = id;
4344 			this.isGlobal = isGlobal;
4345 			addFlag(AstFlag.NON_STATEMENT);
4346 		}
4347 
4348 		private boolean isArray = false;
4349 		private boolean isScalar = false;
4350 
4351 		@Override
4352 		public String toString() {
4353 			return super.toString() + " (" + id + ")";
4354 		}
4355 
4356 		@Override
4357 		public int populateTuples(AwkTuples tuples) {
4358 			pushSourceLineNumber(tuples);
4359 			if (SPECIAL_VAR_NAMES.containsKey(id) && !"ENVIRON".equals(id) && !"ARGV".equals(id)) {
4360 				// Use JRT-managed reads for specials
4361 				switch (id) {
4362 				case "NF":
4363 					tuples.pushNF();
4364 					break;
4365 				case "NR":
4366 					tuples.pushNR();
4367 					break;
4368 				case "FNR":
4369 					tuples.pushFNR();
4370 					break;
4371 				case "FS":
4372 					tuples.pushFS();
4373 					break;
4374 				case "RS":
4375 					tuples.pushRS();
4376 					break;
4377 				case "OFS":
4378 					tuples.pushOFS();
4379 					break;
4380 				case "ORS":
4381 					tuples.pushORS();
4382 					break;
4383 				case "RSTART":
4384 					tuples.pushRSTART();
4385 					break;
4386 				case "RLENGTH":
4387 					tuples.pushRLENGTH();
4388 					break;
4389 				case "FILENAME":
4390 					tuples.pushFILENAME();
4391 					break;
4392 				case "SUBSEP":
4393 					tuples.pushSUBSEP();
4394 					break;
4395 				case "CONVFMT":
4396 					tuples.pushCONVFMT();
4397 					break;
4398 				case "OFMT":
4399 					tuples.pushOFMT();
4400 					break;
4401 				case "ARGC":
4402 					tuples.pushARGC();
4403 					break;
4404 				default:
4405 					throw new Error("Unhandled special var: " + id);
4406 				}
4407 			} else {
4408 				tuples.dereference(offset, isArray(), isGlobal);
4409 			}
4410 			popSourceLineNumber(tuples);
4411 			return 1;
4412 		}
4413 
4414 		@Override
4415 		public boolean isArray() {
4416 			return isArray;
4417 		}
4418 
4419 		@Override
4420 		public boolean isScalar() {
4421 			return isScalar;
4422 		}
4423 
4424 		private boolean isReferenced() {
4425 			return referenced;
4426 		}
4427 
4428 		private void markReferenced() {
4429 			referenced = true;
4430 		}
4431 
4432 		private void setArray(boolean b) {
4433 			isArray = b;
4434 		}
4435 
4436 		private void setScalar(boolean b) {
4437 			isScalar = b;
4438 		}
4439 	}
4440 
4441 	private final class ArrayReferenceAst extends ScalarExpressionAst {
4442 
4443 		private ArrayReferenceAst(AST idAst, AST idxAst) {
4444 			super(idAst, idxAst);
4445 		}
4446 
4447 		private ArrayReferenceAst(int lineNo, AST idAst, AST idxAst) {
4448 			super(lineNo, idAst, idxAst);
4449 		}
4450 
4451 		@Override
4452 		public String toString() {
4453 			return super.toString() + " (" + getAst1() + " [...])";
4454 		}
4455 
4456 		@Override
4457 		public int populateTuples(AwkTuples tuples) {
4458 			pushSourceLineNumber(tuples);
4459 			// get the containing array, autovivifying missing parent subarrays
4460 			populateContainerTuples(tuples);
4461 			// get the index
4462 			getAst2().populateTuples(tuples);
4463 			tuples.dereferenceArray();
4464 			popSourceLineNumber(tuples);
4465 			return 1;
4466 		}
4467 
4468 		private void populateTargetReferenceTuples(AwkTuples tuples) {
4469 			pushSourceLineNumber(tuples);
4470 			populateContainerTuples(tuples);
4471 			getAst2().populateTuples(tuples);
4472 			popSourceLineNumber(tuples);
4473 		}
4474 
4475 		private void populateArrayValueTuples(AwkTuples tuples, boolean createIfMissing) {
4476 			pushSourceLineNumber(tuples);
4477 			populateContainerTuples(tuples);
4478 			getAst2().populateTuples(tuples);
4479 			if (createIfMissing) {
4480 				tuples.ensureArrayElement();
4481 			} else {
4482 				tuples.peekArrayElement();
4483 			}
4484 			popSourceLineNumber(tuples);
4485 		}
4486 
4487 		private void populateTargetValueTuples(AwkTuples tuples) {
4488 			pushSourceLineNumber(tuples);
4489 			populateContainerTuples(tuples);
4490 			getAst2().populateTuples(tuples);
4491 			tuples.dereferenceArray();
4492 			popSourceLineNumber(tuples);
4493 		}
4494 
4495 		private void populateContainerTuples(AwkTuples tuples) {
4496 			if (getAst1() instanceof ArrayReferenceAst) {
4497 				((ArrayReferenceAst) getAst1()).populateArrayValueTuples(tuples, true);
4498 			} else {
4499 				getAst1().populateTuples(tuples);
4500 			}
4501 		}
4502 	}
4503 
4504 	private final class IntegerAst extends ScalarExpressionAst {
4505 
4506 		private Long value;
4507 
4508 		private IntegerAst(Long value) {
4509 			this.value = value;
4510 			addFlag(AstFlag.NON_STATEMENT);
4511 		}
4512 
4513 		@Override
4514 		public String toString() {
4515 			return super.toString() + " (" + value + ")";
4516 		}
4517 
4518 		@Override
4519 		public int populateTuples(AwkTuples tuples) {
4520 			pushSourceLineNumber(tuples);
4521 			tuples.push(value);
4522 			popSourceLineNumber(tuples);
4523 			return 1;
4524 		}
4525 	}
4526 
4527 	/**
4528 	 * Can either assume the role of a double or an integer
4529 	 * by aggressively normalizing the value to an int if possible.
4530 	 */
4531 	private final class DoubleAst extends ScalarExpressionAst {
4532 
4533 		private Object value;
4534 
4535 		private DoubleAst(Double val) {
4536 			double d = val.doubleValue();
4537 			if (d == (int) d) {
4538 				this.value = (int) d;
4539 			} else {
4540 				this.value = d;
4541 			}
4542 			addFlag(AstFlag.NON_STATEMENT);
4543 		}
4544 
4545 		@Override
4546 		public String toString() {
4547 			return super.toString() + " (" + value + ")";
4548 		}
4549 
4550 		@Override
4551 		public int populateTuples(AwkTuples tuples) {
4552 			pushSourceLineNumber(tuples);
4553 			tuples.push(value);
4554 			popSourceLineNumber(tuples);
4555 			return 1;
4556 		}
4557 	}
4558 
4559 	/**
4560 	 * A string is a string; Awk doesn't attempt to normalize
4561 	 * it until it is used in an arithmetic operation!
4562 	 */
4563 	private final class StringAst extends ScalarExpressionAst {
4564 
4565 		private String value;
4566 
4567 		private StringAst(String str) {
4568 			this.value = str;
4569 			addFlag(AstFlag.NON_STATEMENT);
4570 		}
4571 
4572 		@Override
4573 		public String toString() {
4574 			return super.toString() + " (" + value + ")";
4575 		}
4576 
4577 		@Override
4578 		public int populateTuples(AwkTuples tuples) {
4579 			pushSourceLineNumber(tuples);
4580 			tuples.push(value);
4581 			popSourceLineNumber(tuples);
4582 			return 1;
4583 		}
4584 	}
4585 
4586 	private final class RegexpAst extends ScalarExpressionAst {
4587 
4588 		private String regexpStr;
4589 
4590 		private RegexpAst(String regexpStr) {
4591 			this.regexpStr = regexpStr;
4592 		}
4593 
4594 		@Override
4595 		public String toString() {
4596 			return super.toString() + " (" + regexpStr + ")";
4597 		}
4598 
4599 		@Override
4600 		public int populateTuples(AwkTuples tuples) {
4601 			pushSourceLineNumber(tuples);
4602 			tuples.regexp(regexpStr);
4603 			popSourceLineNumber(tuples);
4604 			return 1;
4605 		}
4606 	}
4607 
4608 	private final class ConditionPairAst extends ScalarExpressionAst {
4609 
4610 		private ConditionPairAst(AST booleanAst1, AST booleanAst2) {
4611 			super(booleanAst1, booleanAst2);
4612 		}
4613 
4614 		@Override
4615 		public int populateTuples(AwkTuples tuples) {
4616 			pushSourceLineNumber(tuples);
4617 			getAst1().populateTuples(tuples);
4618 			getAst2().populateTuples(tuples);
4619 			tuples.conditionPair();
4620 			popSourceLineNumber(tuples);
4621 			return 1;
4622 		}
4623 	}
4624 
4625 	private final class BeginAst extends AST {
4626 
4627 		private BeginAst() {
4628 			super();
4629 			setBeginFlag(true);
4630 		}
4631 
4632 		@Override
4633 		public int populateTuples(AwkTuples tuples) {
4634 			pushSourceLineNumber(tuples);
4635 			tuples.push(1);
4636 			popSourceLineNumber(tuples);
4637 			return 1;
4638 		}
4639 	}
4640 
4641 	private final class EndAst extends AST {
4642 
4643 		private EndAst() {
4644 			super();
4645 			setEndFlag(true);
4646 		}
4647 
4648 		@Override
4649 		public int populateTuples(AwkTuples tuples) {
4650 			pushSourceLineNumber(tuples);
4651 			tuples.push(1);
4652 			popSourceLineNumber(tuples);
4653 			return 1;
4654 		}
4655 	}
4656 
4657 	private final class PreIncAst extends ScalarExpressionAst {
4658 
4659 		private PreIncAst(AST symbolAst) {
4660 			super(symbolAst);
4661 		}
4662 
4663 		@Override
4664 		public int populateTuples(AwkTuples tuples) {
4665 			pushSourceLineNumber(tuples);
4666 			if (getAst1() instanceof IDAst) {
4667 				IDAst idAst = (IDAst) getAst1();
4668 				tuples.inc(idAst.offset, idAst.isGlobal);
4669 			} else if (getAst1() instanceof ArrayReferenceAst) {
4670 				ArrayReferenceAst arrAst = (ArrayReferenceAst) getAst1();
4671 				if (arrAst.getAst1() instanceof IDAst) {
4672 					IDAst idAst = (IDAst) arrAst.getAst1();
4673 					if (idAst.isScalar()) {
4674 						throw new SemanticException("Cannot use " + idAst + " as an array.");
4675 					}
4676 					idAst.setArray(true);
4677 				}
4678 				arrAst.populateTargetReferenceTuples(tuples);
4679 				tuples.incMapRef();
4680 			} else if (getAst1() instanceof DollarExpressionAst) {
4681 				DollarExpressionAst dollarExpr = (DollarExpressionAst) getAst1();
4682 				dollarExpr.getAst1().populateTuples(tuples); // OPTIMIATION: duplicate the x in $x here
4683 				// so that it is not evaluated again
4684 				tuples.dup();
4685 				// stack contains eval of dollar arg
4686 				// tuples.assignAsInputField();
4687 				tuples.incDollarRef();
4688 				// OPTIMIATION continued: now evaluate
4689 				// the dollar expression with x (for $x)
4690 				// instead of evaluating the expression again
4691 				tuples.getInputField();
4692 				popSourceLineNumber(tuples);
4693 				return 1; // NOTE, short-circuit return here!
4694 			} else {
4695 				throw new NotImplementedError("unhandled preinc for " + getAst1());
4696 			}
4697 			// else
4698 			// assert false : "cannot refer for preInc to "+getAst1();
4699 			getAst1().populateTuples(tuples);
4700 			popSourceLineNumber(tuples);
4701 			return 1;
4702 		}
4703 	}
4704 
4705 	private final class PreDecAst extends ScalarExpressionAst {
4706 
4707 		private PreDecAst(AST symbolAst) {
4708 			super(symbolAst);
4709 		}
4710 
4711 		@Override
4712 		public int populateTuples(AwkTuples tuples) {
4713 			pushSourceLineNumber(tuples);
4714 			if (getAst1() instanceof IDAst) {
4715 				IDAst idAst = (IDAst) getAst1();
4716 				tuples.dec(idAst.offset, idAst.isGlobal);
4717 			} else if (getAst1() instanceof ArrayReferenceAst) {
4718 				ArrayReferenceAst arrAst = (ArrayReferenceAst) getAst1();
4719 				if (arrAst.getAst1() instanceof IDAst) {
4720 					IDAst idAst = (IDAst) arrAst.getAst1();
4721 					if (idAst.isScalar()) {
4722 						throw new SemanticException("Cannot use " + idAst + " as an array.");
4723 					}
4724 					idAst.setArray(true);
4725 				}
4726 				arrAst.populateTargetReferenceTuples(tuples);
4727 				tuples.decMapRef();
4728 			} else if (getAst1() instanceof DollarExpressionAst) {
4729 				DollarExpressionAst dollarExpr = (DollarExpressionAst) getAst1();
4730 				dollarExpr.getAst1().populateTuples(tuples); // OPTIMIATION: duplicate the x in $x here
4731 				// so that it is not evaluated again
4732 				tuples.dup();
4733 				// stack contains eval of dollar arg
4734 				// tuples.assignAsInputField();
4735 				tuples.decDollarRef();
4736 				// OPTIMIATION continued: now evaluate
4737 				// the dollar expression with x (for $x)
4738 				// instead of evaluating the expression again
4739 				tuples.getInputField();
4740 				popSourceLineNumber(tuples);
4741 				return 1; // NOTE, short-circuit return here!
4742 			} else {
4743 				throw new NotImplementedError("unhandled predec for " + getAst1());
4744 			}
4745 			getAst1().populateTuples(tuples);
4746 			popSourceLineNumber(tuples);
4747 			return 1;
4748 		}
4749 	}
4750 
4751 	private final class PostIncAst extends ScalarExpressionAst {
4752 
4753 		private PostIncAst(AST symbolAst) {
4754 			super(symbolAst);
4755 		}
4756 
4757 		@Override
4758 		public int populateTuples(AwkTuples tuples) {
4759 			pushSourceLineNumber(tuples);
4760 			if (getAst1() instanceof DollarExpressionAst) {
4761 				DollarExpressionAst dollarExpr = (DollarExpressionAst) getAst1();
4762 				dollarExpr.getAst1().populateTuples(tuples);
4763 				tuples.incDollarRef();
4764 			} else {
4765 				if (getAst1() instanceof ArrayReferenceAst) {
4766 					((ArrayReferenceAst) getAst1()).populateTargetValueTuples(tuples);
4767 					tuples.unaryPlus();
4768 				} else {
4769 					getAst1().populateTuples(tuples);
4770 				}
4771 				if (getAst1() instanceof IDAst) {
4772 					IDAst idAst = (IDAst) getAst1();
4773 					tuples.postInc(idAst.offset, idAst.isGlobal);
4774 				} else if (getAst1() instanceof ArrayReferenceAst) {
4775 					ArrayReferenceAst arrAst = (ArrayReferenceAst) getAst1();
4776 					if (arrAst.getAst1() instanceof IDAst) {
4777 						IDAst idAst = (IDAst) arrAst.getAst1();
4778 						if (idAst.isScalar()) {
4779 							throw new SemanticException("Cannot use " + idAst + " as an array.");
4780 						}
4781 						idAst.setArray(true);
4782 					}
4783 					arrAst.populateTargetReferenceTuples(tuples);
4784 					tuples.incMapRef();
4785 				} else {
4786 					throw new NotImplementedError("unhandled postinc for " + getAst1());
4787 				}
4788 			}
4789 			popSourceLineNumber(tuples);
4790 			return 1;
4791 		}
4792 	}
4793 
4794 	private final class PostDecAst extends ScalarExpressionAst {
4795 
4796 		private PostDecAst(AST symbolAst) {
4797 			super(symbolAst);
4798 		}
4799 
4800 		@Override
4801 		public int populateTuples(AwkTuples tuples) {
4802 			pushSourceLineNumber(tuples);
4803 			if (getAst1() instanceof ArrayReferenceAst) {
4804 				((ArrayReferenceAst) getAst1()).populateTargetValueTuples(tuples);
4805 				tuples.unaryPlus();
4806 			} else {
4807 				getAst1().populateTuples(tuples);
4808 			}
4809 			if (getAst1() instanceof IDAst) {
4810 				IDAst idAst = (IDAst) getAst1();
4811 				tuples.postDec(idAst.offset, idAst.isGlobal);
4812 			} else if (getAst1() instanceof ArrayReferenceAst) {
4813 				ArrayReferenceAst arrAst = (ArrayReferenceAst) getAst1();
4814 				if (arrAst.getAst1() instanceof IDAst) {
4815 					IDAst idAst = (IDAst) arrAst.getAst1();
4816 					if (idAst.isScalar()) {
4817 						throw new SemanticException("Cannot use " + idAst + " as an array.");
4818 					}
4819 					idAst.setArray(true);
4820 				}
4821 				arrAst.populateTargetReferenceTuples(tuples);
4822 				tuples.decMapRef();
4823 			} else if (getAst1() instanceof DollarExpressionAst) {
4824 				DollarExpressionAst dollarExpr = (DollarExpressionAst) getAst1();
4825 				dollarExpr.getAst1().populateTuples(tuples);
4826 				tuples.decDollarRef();
4827 			} else {
4828 				throw new NotImplementedError("unhandled postinc for " + getAst1());
4829 			}
4830 			popSourceLineNumber(tuples);
4831 			return 1;
4832 		}
4833 	}
4834 
4835 	private final class PrintAst extends ScalarExpressionAst {
4836 
4837 		private Token outputToken;
4838 		private boolean parenthesized;
4839 
4840 		private PrintAst(AST exprList, Token outToken, AST outputExpr, boolean parenthesized) {
4841 			super(exprList, outputExpr);
4842 			this.outputToken = outToken;
4843 			this.parenthesized = parenthesized;
4844 		}
4845 
4846 		@Override
4847 		public int populateTuples(AwkTuples tuples) {
4848 			pushSourceLineNumber(tuples);
4849 
4850 			int paramCount;
4851 			if (getAst1() == null) {
4852 				if (parenthesized) {
4853 					throw new SemanticException("print() requires at least 1 argument");
4854 				}
4855 				paramCount = 0;
4856 			} else {
4857 				paramCount = getAst1().populateTuples(tuples);
4858 				if (paramCount == 0) {
4859 					throw new SemanticException("Cannot print the result. The expression doesn't return anything.");
4860 				}
4861 			}
4862 
4863 			if (getAst2() != null) {
4864 				getAst2().populateTuples(tuples);
4865 			}
4866 
4867 			if (outputToken == Token.GT) {
4868 				tuples.printToFile(paramCount, false); // false = no append
4869 			} else if (outputToken == Token.APPEND) {
4870 				tuples.printToFile(paramCount, true); // false = no append
4871 			} else if (outputToken == Token.PIPE) {
4872 				tuples.printToPipe(paramCount);
4873 			} else {
4874 				tuples.print(paramCount);
4875 			}
4876 
4877 			popSourceLineNumber(tuples);
4878 			return 0;
4879 		}
4880 	}
4881 
4882 	// we don't know if it is a scalar
4883 	private final class ExtensionAst extends AST {
4884 
4885 		private final ExtensionFunction function;
4886 
4887 		private ExtensionAst(ExtensionFunction functionParam, AST paramAst) {
4888 			super(paramAst);
4889 			this.function = functionParam;
4890 		}
4891 
4892 		@Override
4893 		public int populateTuples(AwkTuples tuples) {
4894 			pushSourceLineNumber(tuples);
4895 			int argCount;
4896 			if (getAst1() == null) {
4897 				argCount = 0;
4898 			} else {
4899 				argCount = countParams((FunctionCallParamListAst) getAst1());
4900 			}
4901 
4902 			int[] reqArrayIdxs = function.collectAssocArrayIndexes(argCount);
4903 
4904 			int paramCount;
4905 			if (getAst1() == null) {
4906 				paramCount = 0;
4907 			} else {
4908 				Set<Integer> arrayIndexes = new HashSet<Integer>();
4909 				for (int idx : reqArrayIdxs) {
4910 					AST paramAst = getParamAst((FunctionCallParamListAst) getAst1(), idx).getAst1();
4911 					if (paramAst instanceof IDAst) {
4912 						IDAst idAst = (IDAst) paramAst;
4913 						if (idAst.isScalar()) {
4914 							throw new SemanticException(
4915 									"Extension '"
4916 											+ function.getKeyword()
4917 											+ "' requires parameter position "
4918 											+ idx
4919 											+ " be an associative array, not a scalar.");
4920 						}
4921 						idAst.setArray(true);
4922 						arrayIndexes.add(Integer.valueOf(idx));
4923 					} else if (paramAst instanceof ArrayReferenceAst) {
4924 						arrayIndexes.add(Integer.valueOf(idx));
4925 					}
4926 				}
4927 
4928 				paramCount = populateActualParameters(
4929 						tuples,
4930 						(FunctionCallParamListAst) getAst1(),
4931 						arrayIndexes,
4932 						0);
4933 			}
4934 			// isInitial == true ::
4935 			// retval of this extension is not a function parameter
4936 			// of another extension
4937 			// true iff Extension | FunctionCallParam | FunctionCallParam | etc.
4938 			boolean isInitial;
4939 			if (getParent() instanceof FunctionCallParamListAst) {
4940 				AST ptr = getParent();
4941 				while (ptr instanceof FunctionCallParamListAst) {
4942 					ptr = ptr.getParent();
4943 				}
4944 				isInitial = !(ptr instanceof ExtensionAst);
4945 			} else {
4946 				isInitial = true;
4947 			}
4948 			tuples.extension(function, paramCount, isInitial);
4949 			popSourceLineNumber(tuples);
4950 			// an extension always returns a value, even if it is blank/null
4951 			return 1;
4952 		}
4953 
4954 		private AST getParamAst(FunctionCallParamListAst pAst, int pos) {
4955 			for (int i = 0; i < pos; ++i) {
4956 				pAst = (FunctionCallParamListAst) pAst.getAst2();
4957 				if (pAst == null) {
4958 					throw new SemanticException("More arguments required for assoc array parameter position specification.");
4959 				}
4960 			}
4961 			return pAst;
4962 		}
4963 
4964 		private int countParams(FunctionCallParamListAst pAst) {
4965 			int cnt = 0;
4966 			while (pAst != null) {
4967 				pAst = (FunctionCallParamListAst) pAst.getAst2();
4968 				++cnt;
4969 			}
4970 			return cnt;
4971 		}
4972 
4973 		@Override
4974 		public String toString() {
4975 			return super.toString() + " (" + function.getKeyword() + ")";
4976 		}
4977 	}
4978 
4979 	private final class PrintfAst extends ScalarExpressionAst {
4980 
4981 		private Token outputToken;
4982 
4983 		private PrintfAst(AST exprList, Token outToken, AST outputExpr) {
4984 			super(exprList, outputExpr);
4985 			this.outputToken = outToken;
4986 		}
4987 
4988 		@Override
4989 		public int populateTuples(AwkTuples tuples) {
4990 			pushSourceLineNumber(tuples);
4991 
4992 			int paramCount;
4993 			if (getAst1() == null) {
4994 				throw new SemanticException("printf requires at least 1 argument");
4995 			} else {
4996 				paramCount = getAst1().populateTuples(tuples);
4997 				if (paramCount == 0) {
4998 					throw new SemanticException("Cannot printf the result. The expression doesn't return anything.");
4999 				}
5000 			}
5001 
5002 			if (getAst2() != null) {
5003 				getAst2().populateTuples(tuples);
5004 			}
5005 
5006 			if (outputToken == Token.GT) {
5007 				tuples.printfToFile(paramCount, false); // false = no append
5008 			} else if (outputToken == Token.APPEND) {
5009 				tuples.printfToFile(paramCount, true); // false = no append
5010 			} else if (outputToken == Token.PIPE) {
5011 				tuples.printfToPipe(paramCount);
5012 			} else {
5013 				tuples.printf(paramCount);
5014 			}
5015 
5016 			popSourceLineNumber(tuples);
5017 			return 0;
5018 		}
5019 	}
5020 
5021 	private final class GetlineAst extends ScalarExpressionAst {
5022 
5023 		private GetlineAst(AST pipeExpr, AST lvalueAst, AST inRedirect) {
5024 			super(pipeExpr, lvalueAst, inRedirect);
5025 		}
5026 
5027 		@Override
5028 		public int populateTuples(AwkTuples tuples) {
5029 			pushSourceLineNumber(tuples);
5030 			if (getAst1() == null && getAst3() == null && getAst2() == null) {
5031 				tuples.getlineInput();
5032 				popSourceLineNumber(tuples);
5033 				return 1;
5034 			}
5035 			if (getAst1() != null) {
5036 				getAst1().populateTuples(tuples);// stack has getAst1() (i.e., "command")
5037 				tuples.useAsCommandInput();
5038 			} else if (getAst3() != null) {
5039 // getline ... < getAst3()
5040 				getAst3().populateTuples(tuples); // stack has getAst3() (i.e., "filename")
5041 				tuples.useAsFileInput();
5042 			} else {
5043 				tuples.getlineInputToTarget();
5044 			}
5045 			// 2 resultant values on the stack!
5046 			// 2nd - -1/0/1 for io-err,eof,success
5047 			// 1st(top) - the input
5048 			if (getAst2() == null) {
5049 				tuples.assignAsInput();
5050 				// stack still has the input, to be popped below...
5051 				// (all assignment results are placed on the stack)
5052 			} else if (getAst2() instanceof IDAst) {
5053 				IDAst idAst = (IDAst) getAst2();
5054 				tuples.assign(idAst.offset, idAst.isGlobal);
5055 				if (idAst.id.equals("RS")) {
5056 					tuples.applyRS();
5057 				}
5058 			} else if (getAst2() instanceof ArrayReferenceAst) {
5059 				ArrayReferenceAst arr = (ArrayReferenceAst) getAst2();
5060 				if (arr.getAst1() instanceof IDAst) {
5061 					IDAst idAst = (IDAst) arr.getAst1();
5062 					if (idAst.isScalar()) {
5063 						throw new SemanticException("Cannot use " + idAst + " as an array.");
5064 					}
5065 					idAst.setArray(true);
5066 				}
5067 				arr.populateTargetReferenceTuples(tuples);
5068 				tuples.assignMapElement();
5069 			} else if (getAst2() instanceof DollarExpressionAst) {
5070 				DollarExpressionAst dollarExpr = (DollarExpressionAst) getAst2();
5071 				if (dollarExpr.getAst2() != null) {
5072 					dollarExpr.getAst2().populateTuples(tuples);
5073 				}
5074 				// stack contains eval of dollar arg
5075 				tuples.assignAsInputField();
5076 			} else {
5077 				throw new SemanticException("Cannot getline into a " + getAst2());
5078 			}
5079 			// get rid of value left by the assignment
5080 			tuples.pop();
5081 			// one value is left on the stack
5082 			popSourceLineNumber(tuples);
5083 			return 1;
5084 		}
5085 	}
5086 
5087 	private final class ReturnStatementAst extends AST {
5088 
5089 		private ReturnStatementAst(AST expr) {
5090 			super(expr);
5091 		}
5092 
5093 		@Override
5094 		public int populateTuples(AwkTuples tuples) {
5095 			pushSourceLineNumber(tuples);
5096 			AST returnable = searchFor(AstFlag.RETURNABLE);
5097 			if (returnable == null) {
5098 				throw new SemanticException("Cannot use return here.");
5099 			}
5100 			if (getAst1() != null) {
5101 				getAst1().populateTuples(tuples);
5102 				tuples.setReturnResult();
5103 			}
5104 			tuples.gotoAddress(returnable.returnAddress());
5105 			popSourceLineNumber(tuples);
5106 			return 0;
5107 		}
5108 	}
5109 
5110 	private final class ExitStatementAst extends AST {
5111 
5112 		private ExitStatementAst(AST expr) {
5113 			super(expr);
5114 		}
5115 
5116 		@Override
5117 		public int populateTuples(AwkTuples tuples) {
5118 			pushSourceLineNumber(tuples);
5119 			if (getAst1() != null) {
5120 				getAst1().populateTuples(tuples);
5121 				tuples.exitWithCode();
5122 			} else {
5123 				tuples.exitWithoutCode();
5124 			}
5125 			popSourceLineNumber(tuples);
5126 			return 0;
5127 		}
5128 	}
5129 
5130 	private final class DeleteStatementAst extends AST {
5131 
5132 		private DeleteStatementAst(AST symbolAst) {
5133 			super(symbolAst);
5134 		}
5135 
5136 		@Override
5137 		public int populateTuples(AwkTuples tuples) {
5138 			pushSourceLineNumber(tuples);
5139 
5140 			if (getAst1() instanceof ArrayReferenceAst) {
5141 				ArrayReferenceAst arrAst = (ArrayReferenceAst) getAst1();
5142 				if (arrAst.getAst1() instanceof IDAst) {
5143 					IDAst idAst = (IDAst) arrAst.getAst1();
5144 					if (idAst.isScalar()) {
5145 						throw new SemanticException("delete: Cannot use a scalar as an array.");
5146 					}
5147 					idAst.setArray(true);
5148 				}
5149 				arrAst.populateTargetReferenceTuples(tuples);
5150 				tuples.deleteMapElement();
5151 			} else if (getAst1() instanceof IDAst) {
5152 				IDAst idAst = (IDAst) getAst1();
5153 				if (idAst.isScalar()) {
5154 					throw new SemanticException("delete: Cannot delete a scalar.");
5155 				}
5156 				idAst.setArray(true);
5157 				tuples.deleteArray(idAst.offset, idAst.isGlobal);
5158 			} else {
5159 				throw new Error("Should never reach here : delete for " + getAst1());
5160 			}
5161 
5162 			popSourceLineNumber(tuples);
5163 			return 0;
5164 		}
5165 	}
5166 
5167 	private class BreakStatementAst extends AST {
5168 
5169 		@Override
5170 		public int populateTuples(AwkTuples tuples) {
5171 			pushSourceLineNumber(tuples);
5172 			AST breakable = searchFor(AstFlag.BREAKABLE);
5173 			if (breakable == null) {
5174 				throw new SemanticException("cannot break; not within a loop");
5175 			}
5176 			tuples.gotoAddress(breakable.breakAddress());
5177 			popSourceLineNumber(tuples);
5178 			return 0;
5179 		}
5180 	}
5181 
5182 	private class NextStatementAst extends AST {
5183 
5184 		@Override
5185 		public int populateTuples(AwkTuples tuples) {
5186 			pushSourceLineNumber(tuples);
5187 			AST nextable = searchFor(AstFlag.NEXTABLE);
5188 			if (nextable == null) {
5189 				throw new SemanticException("cannot next; not within any input rules");
5190 			}
5191 			tuples.gotoAddress(nextable.nextAddress());
5192 			popSourceLineNumber(tuples);
5193 			return 0;
5194 		}
5195 	}
5196 
5197 	private final class ContinueStatementAst extends AST {
5198 
5199 		private ContinueStatementAst() {
5200 			super();
5201 		}
5202 
5203 		@Override
5204 		public int populateTuples(AwkTuples tuples) {
5205 			pushSourceLineNumber(tuples);
5206 			AST continueable = searchFor(AstFlag.CONTINUEABLE);
5207 			if (continueable == null) {
5208 				throw new SemanticException("cannot issue a continue; not within any loops");
5209 			}
5210 			tuples.gotoAddress(continueable.continueAddress());
5211 			popSourceLineNumber(tuples);
5212 			return 0;
5213 		}
5214 	}
5215 
5216 	// this was static...
5217 	// made non-static to throw a meaningful ParserException when necessary
5218 	private final class FunctionProxy implements Supplier<Address> {
5219 
5220 		private FunctionDefAst functionDefAst;
5221 		private String id;
5222 
5223 		private FunctionProxy(String id) {
5224 			this.id = id;
5225 		}
5226 
5227 		private void setFunctionDefinition(FunctionDefAst functionDef) {
5228 			if (functionDefAst != null) {
5229 				throw parserException("function " + functionDef + " already defined");
5230 			} else {
5231 				functionDefAst = functionDef;
5232 			}
5233 		}
5234 
5235 		private boolean isDefined() {
5236 			return functionDefAst != null;
5237 		}
5238 
5239 		@Override
5240 		public Address get() {
5241 			return functionDefAst.getAddress();
5242 		}
5243 
5244 		private String getFunctionName() {
5245 			return id;
5246 		}
5247 
5248 		private int getFunctionParamCount() {
5249 			return functionDefAst.paramCount();
5250 		}
5251 
5252 		@Override
5253 		public String toString() {
5254 			return super.toString() + " (" + id + ")";
5255 		}
5256 
5257 		private void checkActualToFormalParameters(AST actualParams) {
5258 			functionDefAst.checkActualToFormalParameters(actualParams);
5259 		}
5260 	}
5261 
5262 	/**
5263 	 * Adds {varName -&gt; offset} mappings to the tuples so that global variables
5264 	 * can be set by the interpreter while processing filename and name=value
5265 	 * entries from the command-line.
5266 	 * Also sends function names to the tuples, to provide the back end
5267 	 * with names to invalidate if name=value assignments are passed
5268 	 * in via the -v or ARGV arguments.
5269 	 *
5270 	 * @param tuples The tuples to add the mapping to.
5271 	 */
5272 	public void populateGlobalVariableNameToOffsetMappings(AwkTuples tuples) {
5273 		for (String varname : symbolTable.globalIds.keySet()) {
5274 			IDAst idAst = symbolTable.globalIds.get(varname);
5275 			// The last arg originally was ", idAst.isScalar", but this is not set true
5276 			// if the variable use is ambiguous. Therefore, assume it is a scalar
5277 			// if it's Token.NOT used as an array.
5278 			tuples.addGlobalVariableNameToOffsetMapping(varname, idAst.offset, idAst.isArray);
5279 		}
5280 		tuples.setFunctionNameSet(symbolTable.functionProxies.keySet());
5281 	}
5282 
5283 	private class AwkSymbolTableImpl {
5284 
5285 		int numGlobals() {
5286 			return globalIds.size();
5287 		}
5288 
5289 		// "constants"
5290 		private BeginAst beginAst = null;
5291 		private EndAst endAst = null;
5292 
5293 		// functions (proxies)
5294 		private Map<String, FunctionProxy> functionProxies = new HashMap<String, FunctionProxy>();
5295 
5296 		// variable management
5297 		private Map<String, IDAst> globalIds = new HashMap<String, IDAst>();
5298 		private Map<String, Map<String, IDAst>> localIds = new HashMap<String, Map<String, IDAst>>();
5299 		private Map<String, Set<String>> functionParameters = new HashMap<String, Set<String>>();
5300 		private Set<String> ids = new HashSet<String>();
5301 
5302 		// current function definition for symbols
5303 		private String currentFunctionName = null;
5304 
5305 		// using set/clear rather than push/pop, it is impossible to define functions within functions
5306 		void setFunctionName(String functionName) {
5307 			this.currentFunctionName = functionName;
5308 		}
5309 
5310 		void clearFunctionName(String functionName) {
5311 			this.currentFunctionName = null;
5312 		}
5313 
5314 		AST addBEGIN() {
5315 			if (beginAst == null) {
5316 				beginAst = new BeginAst();
5317 			}
5318 			return beginAst;
5319 		}
5320 
5321 		AST addEND() {
5322 			if (endAst == null) {
5323 				endAst = new EndAst();
5324 			}
5325 			return endAst;
5326 		}
5327 
5328 		private IDAst getID(String id) {
5329 			if (functionProxies.get(id) != null) {
5330 				throw parserException("cannot use " + id + " as a variable; it is a function");
5331 			}
5332 
5333 			// put in the pool of ids to guard against using it as a function name
5334 			ids.add(id);
5335 
5336 			Map<String, IDAst> map;
5337 			if (currentFunctionName == null) {
5338 				map = globalIds;
5339 			} else {
5340 				Set<String> set = functionParameters.get(currentFunctionName);
5341 				// we need "set != null && ..." here because if function
5342 				// is defined with no args (i.e., function f() ...),
5343 				// then set is null
5344 				if (set != null && set.contains(id)) {
5345 					map = localIds.get(currentFunctionName);
5346 					if (map == null) {
5347 						map = new HashMap<String, IDAst>();
5348 						localIds.put(currentFunctionName, map);
5349 					}
5350 				} else {
5351 					map = globalIds;
5352 				}
5353 			}
5354 			IDAst idAst = map.get(id);
5355 			if (idAst == null) {
5356 				idAst = new IDAst(id, map == globalIds);
5357 				idAst.offset = map.size();
5358 				map.put(id, idAst);
5359 			}
5360 			return idAst;
5361 		}
5362 
5363 		AST addID(String id) throws ParserException {
5364 			IDAst retVal = getID(id);
5365 			retVal.markReferenced();
5366 			/// ***
5367                         /// We really don't know if the evaluation is for an array or for a scalar
5368                         /// here, because we can use an array as a function parameter (passed by reference).
5369 			/// ***
5370 			// if (retVal.isArray)
5371 			// throw parserException("Cannot use "+retVal+" as a scalar.");
5372 			// retVal.isScalar = true;
5373 			return retVal;
5374 		}
5375 
5376 		int addFunctionParameter(String functionName, String id) {
5377 			Set<String> set = functionParameters.get(functionName);
5378 			if (set == null) {
5379 				set = new HashSet<String>();
5380 				functionParameters.put(functionName, set);
5381 			}
5382 			if (set.contains(id)) {
5383 				throw parserException("multiply defined parameter " + id + " in function " + functionName);
5384 			}
5385 			int retval = set.size();
5386 			set.add(id);
5387 			Map<String, IDAst> map = localIds.get(functionName);
5388 			if (map == null) {
5389 				map = new HashMap<String, IDAst>();
5390 				localIds.put(functionName, map);
5391 			}
5392 			IDAst idAst = map.get(id);
5393 			if (idAst == null) {
5394 				idAst = new IDAst(id, map == globalIds);
5395 				idAst.offset = map.size();
5396 				map.put(id, idAst);
5397 			}
5398 
5399 			return retval;
5400 		}
5401 
5402 		IDAst getFunctionParameterIDAST(String functionName, String fIdString) {
5403 			return localIds.get(functionName).get(fIdString);
5404 		}
5405 
5406 		AST addArrayID(String id) throws ParserException {
5407 			IDAst retVal = getID(id);
5408 			retVal.markReferenced();
5409 			if (retVal.isScalar()) {
5410 				throw parserException("Cannot use " + retVal + " as an array.");
5411 			}
5412 			retVal.setArray(true);
5413 			return retVal;
5414 		}
5415 
5416 		AST addFunctionDef(String functionName, AST paramList, AST block) {
5417 			if (ids.contains(functionName)) {
5418 				throw parserException("cannot use " + functionName + " as a function; it is a variable");
5419 			}
5420 			FunctionProxy functionProxy = functionProxies.get(functionName);
5421 			if (functionProxy == null) {
5422 				functionProxy = new FunctionProxy(functionName);
5423 				functionProxies.put(functionName, functionProxy);
5424 			}
5425 			FunctionDefAst functionDef = new FunctionDefAst(functionName, paramList, block);
5426 			functionProxy.setFunctionDefinition(functionDef);
5427 			return functionDef;
5428 		}
5429 
5430 		AST addFunctionCall(String id, AST paramList) {
5431 			FunctionProxy functionProxy = functionProxies.get(id);
5432 			if (functionProxy == null) {
5433 				functionProxy = new FunctionProxy(id);
5434 				functionProxies.put(id, functionProxy);
5435 			}
5436 			return new FunctionCallAst(functionProxy, paramList);
5437 		}
5438 
5439 		AST addArrayReference(String id, AST idxAst, int lineNo) throws ParserException {
5440 			return new ArrayReferenceAst(lineNo, addArrayID(id), idxAst);
5441 		}
5442 
5443 		// constants are no longer cached/hashed so that individual ASTs
5444 		// can report accurate line numbers upon errors
5445 
5446 		AST addINTEGER(String integer) {
5447 			return new IntegerAst(Long.parseLong(integer));
5448 		}
5449 
5450 		AST addDOUBLE(String dbl) {
5451 			return new DoubleAst(Double.valueOf(dbl));
5452 		}
5453 
5454 		AST addSTRING(String str) {
5455 			return new StringAst(str);
5456 		}
5457 
5458 		AST addREGEXP(String localRegexp) {
5459 			return new RegexpAst(localRegexp);
5460 		}
5461 	}
5462 
5463 	private ParserException parserException(String msg) {
5464 		return new ParserException(
5465 				msg,
5466 				scriptSources.get(scriptSourcesCurrentIndex).getDescription(),
5467 				reader.getLineNumber());
5468 	}
5469 }