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