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