View Javadoc
1   package org.metricshub.jawk.backend;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * Jawk
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2006 - 2025 MetricsHub
8    * ჻჻჻჻჻჻
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23   */
24  
25  import java.io.IOException;
26  import java.io.PrintStream;
27  import java.io.StringReader;
28  import java.math.BigDecimal;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.Enumeration;
32  import java.util.HashMap;
33  import java.util.LinkedList;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.LinkedHashSet;
39  import java.util.StringTokenizer;
40  import java.util.Deque;
41  import java.util.regex.Matcher;
42  import java.util.regex.Pattern;
43  import org.metricshub.jawk.AwkSandboxException;
44  import org.metricshub.jawk.ExitException;
45  import org.metricshub.jawk.ext.AbstractExtension;
46  import org.metricshub.jawk.ext.ExtensionFunction;
47  import org.metricshub.jawk.ext.JawkExtension;
48  import org.metricshub.jawk.frontend.AstNode;
49  import org.metricshub.jawk.intermediate.Address;
50  import org.metricshub.jawk.intermediate.AwkTuples;
51  import org.metricshub.jawk.intermediate.Opcode;
52  import org.metricshub.jawk.intermediate.PositionTracker;
53  import org.metricshub.jawk.intermediate.UninitializedObject;
54  import org.metricshub.jawk.jrt.AssocArray;
55  import org.metricshub.jawk.jrt.AwkRuntimeException;
56  import org.metricshub.jawk.jrt.BlockManager;
57  import org.metricshub.jawk.jrt.BlockObject;
58  import org.metricshub.jawk.jrt.CharacterTokenizer;
59  import org.metricshub.jawk.jrt.ConditionPair;
60  import org.metricshub.jawk.jrt.JRT;
61  import java.util.ArrayDeque;
62  import org.metricshub.jawk.jrt.RegexTokenizer;
63  import org.metricshub.jawk.jrt.SingleCharacterTokenizer;
64  import org.metricshub.jawk.jrt.VariableManager;
65  import org.metricshub.jawk.util.AwkSettings;
66  import org.metricshub.jawk.util.ScriptSource;
67  import org.metricshub.jawk.jrt.BSDRandom;
68  import org.metricshub.printf4j.Printf4J;
69  
70  /**
71   * The Jawk interpreter.
72   * <p>
73   * It takes tuples constructed by the intermediate step
74   * and executes each tuple in accordance to their instruction semantics.
75   * The tuples correspond to the Awk script compiled by the parser.
76   * The interpreter consists of an instruction processor (interpreter),
77   * a runtime stack, and machinery to support the instruction set
78   * contained within the tuples.
79   * <p>
80   * The interpreter runs completely independent of the frontend/intermediate step.
81   * In fact, an intermediate file produced by Jawk is sufficient to
82   * execute on this interpreter. The binding data-structure is
83   * the {@link AwkSettings}, which can contain options pertinent to
84   * the interpreter. For example, the interpreter must know about
85   * the -v command line argument values, as well as the file/variable list
86   * parameter values (ARGC/ARGV) after the script on the command line.
87   * However, if programmatic access to the AVM is required, meaningful
88   * {@link AwkSettings} are not required.
89   * <p>
90   * Semantic analysis has occurred prior to execution of the interpreter.
91   * Therefore, the interpreter throws AwkRuntimeExceptions upon most
92   * errors/conditions. It can also throw a <code>java.lang.Error</code> if an
93   * interpreter error is encountered.
94   *
95   * @author Danny Daglas
96   */
97  public class AVM implements VariableManager {
98  
99  	private static final boolean IS_WINDOWS = System.getProperty("os.name").indexOf("Windows") >= 0;
100 
101 	private RuntimeStack runtimeStack = new RuntimeStack();
102 
103 	// operand stack
104 	private Deque<Object> operandStack = new LinkedList<Object>();
105 	private List<String> arguments;
106 	private boolean sortedArrayKeys;
107 	private Map<String, Object> initialVariables;
108 	private String initialFsValue;
109 	private boolean trapIllegalFormatExceptions;
110 	private JRT jrt;
111 	private final Locale locale;
112 	private Map<String, JawkExtension> extensionInstances;
113 
114 	private Map<String, ExtensionFunction> extensionFunctions;
115 
116 	// stack methods
117 	// private Object pop() { return operandStack.removeFirst(); }
118 	// private void push(Object o) { operandStack.addLast(o); }
119 	private Object pop() {
120 		return operandStack.pop();
121 	}
122 
123 	private void push(Object o) {
124 		operandStack.push(o);
125 	}
126 
127 	private final AwkSettings settings;
128 
129 	/**
130 	 * Construct the interpreter.
131 	 * <p>
132 	 * Provided to allow programmatic construction of the interpreter
133 	 * outside of the framework which is used by Jawk.
134 	 */
135 	public AVM() {
136 		this(null, Collections.<String, JawkExtension>emptyMap(), Collections.<String, ExtensionFunction>emptyMap());
137 	}
138 
139 	/**
140 	 * Construct the interpreter, accepting parameters which may have been
141 	 * set on the command-line arguments to the JVM.
142 	 *
143 	 * @param parameters The parameters affecting the behavior of the
144 	 *        interpreter.
145 	 * @param extensionInstances Map of the extensions to load
146 	 * @param extensionFunctions Map of extension functions available for parsing
147 	 */
148 	public AVM(final AwkSettings parameters,
149 			final Map<String, JawkExtension> extensionInstances,
150 			final Map<String, ExtensionFunction> extensionFunctions) {
151 		boolean hasProvidedSettings = parameters != null;
152 		this.settings = hasProvidedSettings ? parameters : AwkSettings.DEFAULT_SETTINGS;
153 		this.extensionInstances = extensionInstances == null ?
154 				Collections.<String, JawkExtension>emptyMap() : extensionInstances;
155 		this.extensionFunctions = extensionFunctions == null ?
156 				Collections.<String, ExtensionFunction>emptyMap() : extensionFunctions;
157 
158 		locale = this.settings.getLocale();
159 		arguments = this.settings.getNameValueOrFileNames();
160 		sortedArrayKeys = this.settings.isUseSortedArrayKeys();
161 		initialVariables = this.settings.getVariables();
162 		initialFsValue = this.settings.getFieldSeparator();
163 		trapIllegalFormatExceptions = hasProvidedSettings
164 				&& this.settings.isCatchIllegalFormatExceptions();
165 
166 		jrt = createJrt();
167 		jrt.setStreams(settings.getOutputStream(), System.err);
168 		initExtensions();
169 	}
170 
171 	protected JRT createJrt() {
172 		return new JRT(this);
173 	}
174 
175 	protected AwkTuples createTuples() {
176 		return new AwkTuples();
177 	}
178 
179 	protected AVM createSubAvm(
180 			AwkSettings parameters,
181 			Map<String, JawkExtension> subExtensionInstances,
182 			Map<String, ExtensionFunction> subExtensionFunctions) {
183 		return new AVM(parameters, subExtensionInstances, subExtensionFunctions);
184 	}
185 
186 	private void initExtensions() {
187 		if (extensionInstances.isEmpty()) {
188 			return;
189 		}
190 		Set<JawkExtension> initialized = new LinkedHashSet<JawkExtension>();
191 		for (JawkExtension extension : extensionInstances.values()) {
192 			if (initialized.add(extension)) {
193 				extension.init(this, jrt, settings); // this = VariableManager
194 			}
195 		}
196 	}
197 
198 	private long nfOffset = NULL_OFFSET;
199 	private long nrOffset = NULL_OFFSET;
200 	private long fnrOffset = NULL_OFFSET;
201 	private long fsOffset = NULL_OFFSET;
202 	private long rsOffset = NULL_OFFSET;
203 	private long ofsOffset = NULL_OFFSET;
204 	private long orsOffset = NULL_OFFSET;
205 	private long rstartOffset = NULL_OFFSET;
206 	private long rlengthOffset = NULL_OFFSET;
207 	private long filenameOffset = NULL_OFFSET;
208 	private long subsepOffset = NULL_OFFSET;
209 	private long convfmtOffset = NULL_OFFSET;
210 	private long ofmtOffset = NULL_OFFSET;
211 	private long environOffset = NULL_OFFSET;
212 	private long argcOffset = NULL_OFFSET;
213 	private long argvOffset = NULL_OFFSET;
214 
215 	private static final Integer ZERO = Integer.valueOf(0);
216 	private static final Integer ONE = Integer.valueOf(1);
217 
218 	/** Random number generator used for rand() */
219 	private final BSDRandom randomNumberGenerator = new BSDRandom(1);
220 
221 	/**
222 	 * Last seed value used with {@code srand()}.
223 	 * <p>
224 	 * The default seed for {@code rand()} in One True Awk is {@code 1}, so
225 	 * we initialize {@code oldseed} with this value to mimic that
226 	 * behaviour. This ensures deterministic sequences until the user
227 	 * explicitly calls {@code srand()}.
228 	 */
229 	private int oldseed = 1;
230 
231 	private Address exitAddress = null;
232 
233 	/**
234 	 * <code>true</code> if execution position is within an END block;
235 	 * <code>false</code> otherwise.
236 	 */
237 	private boolean withinEndBlocks = false;
238 
239 	/**
240 	 * Exit code set by the <code>exit NN</code> command (0 by default)
241 	 */
242 	private int exitCode = 0;
243 
244 	/**
245 	 * Whether <code>exit</code> has been called and we should throw ExitException
246 	 */
247 	private boolean throwExitException = false;
248 
249 	/**
250 	 * Maps global variable names to their global array offsets.
251 	 * It is useful when passing variable assignments from the file-list
252 	 * portion of the command-line arguments.
253 	 */
254 	private Map<String, Integer> globalVariableOffsets;
255 	/**
256 	 * Indicates whether the variable, by name, is a scalar
257 	 * or not. If not, then it is an Associative Array.
258 	 */
259 	private Map<String, Boolean> globalVariableArrays;
260 	private Set<String> functionNames;
261 
262 	/**
263 	 * Evaluate the provided tuples as an AWK expression.
264 	 *
265 	 * @param tuples Tuples representing the expression
266 	 * @param input Optional input line used to populate $0 and related fields
267 	 * @return The resulting value of the expression
268 	 * @throws IOException if an IO error occurs during evaluation
269 	 */
270 	public Object eval(AwkTuples tuples, String input) throws IOException {
271 		jrt.assignInitialVariables(initialVariables);
272 
273 		// Now execute the tuples
274 		try {
275 			interpret(tuples);
276 		} catch (ExitException e) {
277 			// Special case (which should never happen):
278 			// return the value of the "exit" statement if any
279 			return e.getCode();
280 		}
281 
282 		// Return the top of the stack, which is the value of the specified expression
283 		return operandStack.size() == 0 ? null : pop();
284 	}
285 
286 	private static int parseIntField(Object obj, PositionTracker position) {
287 		if (obj instanceof Number) {
288 			double num = ((Number) obj).doubleValue();
289 			if (num < 0) {
290 				throw new AwkRuntimeException(position.lineNumber(), "Field $(" + obj.toString() + ") is incorrect.");
291 			}
292 			return (int) num;
293 		}
294 
295 		String str = obj.toString();
296 		if (str.isEmpty()) {
297 			return 0;
298 		}
299 
300 		try {
301 			double num = new BigDecimal(str).doubleValue();
302 			if (num < 0) {
303 				throw new AwkRuntimeException(position.lineNumber(), "Field $(" + obj.toString() + ") is incorrect.");
304 			}
305 			return (int) num;
306 		} catch (NumberFormatException nfe) {
307 			return 0;
308 		}
309 	}
310 
311 	private void setNumOnJRT(int fieldNum, double num) {
312 		String numString;
313 		if (JRT.isActuallyLong(num)) {
314 			numString = Long.toString((long) Math.rint(num));
315 		} else {
316 			numString = Double.toString(num);
317 		}
318 
319 		// same code as ASSIGN_AS_INPUT_FIELD
320 		if (fieldNum == 0) {
321 			jrt.setInputLine(numString.toString());
322 			jrt.jrtParseFields();
323 		} else {
324 			jrt.jrtSetInputField(numString, fieldNum);
325 		}
326 	}
327 
328 	private String execSubOrGSub(PositionTracker position, int gsubArgPos) {
329 		String newString;
330 
331 		// arg[gsubArgPos] = isGsub
332 		// stack[0] = original field value
333 		// stack[1] = replacement string
334 		// stack[2] = ere
335 		boolean isGsub = position.boolArg(gsubArgPos);
336 		String convfmt = getCONVFMT().toString();
337 		String orig = JRT.toAwkString(pop(), convfmt, locale);
338 		String repl = JRT.toAwkString(pop(), convfmt, locale);
339 		String ere = JRT.toAwkString(pop(), convfmt, locale);
340 		if (isGsub) {
341 			newString = replaceAll(orig, ere, repl);
342 		} else {
343 			newString = replaceFirst(orig, ere, repl);
344 		}
345 
346 		return newString;
347 	}
348 
349 	/**
350 	 * Traverse the tuples, executing their associated opcodes to provide
351 	 * an execution platform for Jawk scripts.
352 	 *
353 	 * @throws IOException in case of I/O problems (with getline typically)
354 	 */
355 	public void interpret(AwkTuples tuples) throws ExitException, IOException {
356 		Map<String, Pattern> regexps = new HashMap<String, Pattern>();
357 		Map<Integer, ConditionPair> conditionPairs = new HashMap<Integer, ConditionPair>();
358 
359 		globalVariableOffsets = tuples.getGlobalVariableOffsetMap();
360 		globalVariableArrays = tuples.getGlobalVariableAarrayMap();
361 		functionNames = tuples.getFunctionNameSet();
362 
363 		PositionTracker position = tuples.top();
364 
365 		try {
366 			while (!position.isEOF()) {
367 				// System_out.println("--> "+position);
368 				Opcode opcode = position.opcode();
369 				// switch on OPCODE
370 				switch (opcode) {
371 				case PRINT: {
372 					// arg[0] = # of items to print on the stack
373 					// stack[0] = item 1
374 					// stack[1] = item 2
375 					// etc.
376 					long numArgs = position.intArg(0);
377 					printTo(settings.getOutputStream(), numArgs);
378 					position.next();
379 					break;
380 				}
381 				case PRINT_TO_FILE: {
382 // arg[0] = # of items to print on the stack
383 					// arg[1] = true=append, false=overwrite
384 					// stack[0] = output filename
385 					// stack[1] = item 1
386 					// stack[2] = item 2
387 					// etc.
388 					long numArgs = position.intArg(0);
389 					boolean append = position.boolArg(1);
390 					String key = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
391 					PrintStream ps = jrt.jrtGetPrintStream(key, append);
392 					printTo(ps, numArgs);
393 					position.next();
394 					break;
395 				}
396 				case PRINT_TO_PIPE: {
397 // arg[0] = # of items to print on the stack
398 					// stack[0] = command to execute
399 					// stack[1] = item 1
400 					// stack[2] = item 2
401 					// etc.
402 					long numArgs = position.intArg(0);
403 					String cmd = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
404 					PrintStream ps = jrt.jrtSpawnForOutput(cmd);
405 					printTo(ps, numArgs);
406 					position.next();
407 					break;
408 				}
409 				case PRINTF: {
410 					// arg[0] = # of items to print on the stack (includes format string)
411 					// stack[0] = format string
412 					// stack[1] = item 1
413 					// etc.
414 					long numArgs = position.intArg(0);
415 					printfTo(settings.getOutputStream(), numArgs);
416 					position.next();
417 					break;
418 				}
419 				case PRINTF_TO_FILE: {
420 // arg[0] = # of items to print on the stack (includes format string)
421 					// arg[1] = true=append, false=overwrite
422 					// stack[0] = output filename
423 					// stack[1] = format string
424 					// stack[2] = item 1
425 					// etc.
426 					long numArgs = position.intArg(0);
427 					boolean append = position.boolArg(1);
428 					String key = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
429 					PrintStream ps = jrt.jrtGetPrintStream(key, append);
430 					printfTo(ps, numArgs);
431 					position.next();
432 					break;
433 				}
434 				case PRINTF_TO_PIPE: {
435 // arg[0] = # of items to print on the stack (includes format string)
436 					// stack[0] = command to execute
437 					// stack[1] = format string
438 					// stack[2] = item 1
439 					// etc.
440 					long numArgs = position.intArg(0);
441 					String cmd = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
442 					PrintStream ps = jrt.jrtSpawnForOutput(cmd);
443 					printfTo(ps, numArgs);
444 					position.next();
445 					break;
446 				}
447 				case SPRINTF: {
448 					// arg[0] = # of sprintf arguments
449 					// stack[0] = arg1 (format string)
450 					// stack[1] = arg2
451 					// etc.
452 					long numArgs = position.intArg(0);
453 					push(sprintfFunction(numArgs));
454 					position.next();
455 					break;
456 				}
457 				case LENGTH: {
458 					// arg[0] = 0==use $0, otherwise, use the stack element
459 					// stack[0] = element to measure (only if arg[0] != 0)
460 
461 					// print items from the top of the stack
462 					// # of items
463 					long num = position.intArg(0);
464 					if (num == 0) {
465 						// display $0
466 						push(jrt.jrtGetInputField(0).toString().length());
467 					} else {
468 						push(pop().toString().length());
469 					}
470 					position.next();
471 					break;
472 				}
473 				case PUSH: {
474 					// arg[0] = constant to push onto the stack
475 					push(position.arg(0));
476 					position.next();
477 					break;
478 				}
479 				case POP: {
480 					// stack[0] = item to pop from the stack
481 					pop();
482 					position.next();
483 					break;
484 				}
485 				case IFFALSE: {
486 					// arg[0] = address to jump to if top of stack is false
487 					// stack[0] = item to check
488 
489 					// if int, then check for 0
490 					// if double, then check for 0
491 					// if String, then check for "" or double value of "0"
492 					boolean jump = !jrt.toBoolean(pop());
493 					if (jump) {
494 						position.jump(position.addressArg());
495 					} else {
496 						position.next();
497 					}
498 					break;
499 				}
500 				case TO_NUMBER: {
501 					// stack[0] = item to convert to a number
502 
503 					// if int, then check for 0
504 					// if double, then check for 0
505 					// if String, then check for "" or double value of "0"
506 					boolean val = jrt.toBoolean(pop());
507 					push(val ? ONE : ZERO);
508 					position.next();
509 					break;
510 				}
511 				case IFTRUE: {
512 					// arg[0] = address to jump to if top of stack is true
513 					// stack[0] = item to check
514 
515 					// if int, then check for 0
516 					// if double, then check for 0
517 					// if String, then check for "" or double value of "0"
518 					boolean jump = jrt.toBoolean(pop());
519 					if (jump) {
520 						position.jump(position.addressArg());
521 					} else {
522 						position.next();
523 					}
524 					break;
525 				}
526 				case NOT: {
527 					// stack[0] = item to logically negate
528 
529 					Object o = pop();
530 
531 					boolean result = jrt.toBoolean(o);
532 
533 					if (result) {
534 						push(0);
535 					} else {
536 						push(1);
537 					}
538 					position.next();
539 					break;
540 				}
541 				case NEGATE: {
542 					// stack[0] = item to numerically negate
543 
544 					double d = JRT.toDouble(pop());
545 					if (JRT.isActuallyLong(d)) {
546 						push((long) -Math.rint(d));
547 					} else {
548 						push(-d);
549 					}
550 					position.next();
551 					break;
552 				}
553 				case UNARY_PLUS: {
554 					// stack[0] = item to convert to a number
555 					double d = JRT.toDouble(pop());
556 					if (JRT.isActuallyLong(d)) {
557 						push((long) Math.rint(d));
558 					} else {
559 						push(d);
560 					}
561 					position.next();
562 					break;
563 				}
564 				case GOTO: {
565 					// arg[0] = address
566 
567 					position.jump(position.addressArg());
568 					break;
569 				}
570 				case NOP: {
571 					// do nothing, just advance the position
572 					position.next();
573 					break;
574 				}
575 				case CONCAT: {
576 					// stack[0] = string1
577 					// stack[1] = string2
578 					String convfmt = getCONVFMT().toString();
579 					String s2 = JRT.toAwkString(pop(), convfmt, locale);
580 					String s1 = JRT.toAwkString(pop(), convfmt, locale);
581 					String resultString = s1 + s2;
582 					push(resultString);
583 					position.next();
584 					break;
585 				}
586 				case ASSIGN: {
587 					// arg[0] = offset
588 					// arg[1] = isGlobal
589 					// stack[0] = value
590 					Object value = pop();
591 					boolean isGlobal = position.boolArg(1);
592 					assign(position.intArg(0), value, isGlobal, position);
593 					position.next();
594 					break;
595 				}
596 				case ASSIGN_ARRAY: {
597 					// arg[0] = offset
598 					// arg[1] = isGlobal
599 					// stack[0] = array index
600 					// stack[1] = value
601 					Object arrIdx = pop();
602 					Object rhs = pop();
603 					if (rhs == null) {
604 						rhs = BLANK;
605 					}
606 					long offset = position.intArg(0);
607 					boolean isGlobal = position.boolArg(1);
608 					assignArray(offset, arrIdx, rhs, isGlobal);
609 					position.next();
610 					break;
611 				}
612 				case PLUS_EQ_ARRAY:
613 				case MINUS_EQ_ARRAY:
614 				case MULT_EQ_ARRAY:
615 				case DIV_EQ_ARRAY:
616 				case MOD_EQ_ARRAY:
617 				case POW_EQ_ARRAY: {
618 					// arg[0] = offset
619 					// arg[1] = isGlobal
620 					// stack[0] = array index
621 					// stack[1] = value
622 					Object arrIdx = pop();
623 					Object rhs = pop();
624 					if (rhs == null) {
625 						rhs = BLANK;
626 					}
627 					long offset = position.intArg(0);
628 					boolean isGlobal = position.boolArg(1);
629 
630 					double val = JRT.toDouble(rhs);
631 
632 					// from DEREF_ARRAY
633 					// stack[0] = AssocArray
634 					// stack[1] = array index
635 					Object o1 = runtimeStack.getVariable(offset, isGlobal); // map
636 					if (o1 == null || o1 instanceof UninitializedObject) {
637 						o1 = new AssocArray(sortedArrayKeys);
638 						runtimeStack.setVariable(offset, o1, isGlobal);
639 					} else {
640 						assert o1 instanceof AssocArray;
641 					}
642 
643 					AssocArray array = (AssocArray) o1;
644 					Object o = array.get(arrIdx);
645 					assert o != null;
646 					double origVal = JRT.toDouble(o);
647 
648 					double newVal;
649 
650 					switch (opcode) {
651 					case PLUS_EQ_ARRAY:
652 						newVal = origVal + val;
653 						break;
654 					case MINUS_EQ_ARRAY:
655 						newVal = origVal - val;
656 						break;
657 					case MULT_EQ_ARRAY:
658 						newVal = origVal * val;
659 						break;
660 					case DIV_EQ_ARRAY:
661 						newVal = origVal / val;
662 						break;
663 					case MOD_EQ_ARRAY:
664 						newVal = origVal % val;
665 						break;
666 					case POW_EQ_ARRAY:
667 						newVal = Math.pow(origVal, val);
668 						break;
669 					default:
670 						throw new Error("Invalid op code here: " + opcode);
671 					}
672 
673 					if (JRT.isActuallyLong(newVal)) {
674 						assignArray(offset, arrIdx, (long) Math.rint(newVal), isGlobal);
675 					} else {
676 						assignArray(offset, arrIdx, newVal, isGlobal);
677 					}
678 					position.next();
679 					break;
680 				}
681 
682 				case ASSIGN_AS_INPUT: {
683 					// stack[0] = value
684 					jrt.setInputLine(pop().toString());
685 					jrt.jrtParseFields();
686 					push(jrt.getInputLine());
687 					position.next();
688 					break;
689 				}
690 
691 				case ASSIGN_AS_INPUT_FIELD: {
692 					// stack[0] = field number
693 					// stack[1] = value
694 					Object fieldNumObj = pop();
695 					int fieldNum;
696 					if (fieldNumObj instanceof Number) {
697 						fieldNum = ((Number) fieldNumObj).intValue();
698 					} else {
699 						try {
700 							fieldNum = Integer.parseInt(fieldNumObj.toString());
701 						} catch (NumberFormatException nfe) {
702 							fieldNum = 0;
703 						}
704 					}
705 					String value = pop().toString();
706 					push(value); // leave the result on the stack
707 					if (fieldNum == 0) {
708 						jrt.setInputLine(value);
709 						jrt.jrtParseFields();
710 					} else {
711 						jrt.jrtSetInputField(value, fieldNum);
712 					}
713 					position.next();
714 					break;
715 				}
716 				case PLUS_EQ:
717 				case MINUS_EQ:
718 				case MULT_EQ:
719 				case DIV_EQ:
720 				case MOD_EQ:
721 				case POW_EQ: {
722 					// arg[0] = offset
723 					// arg[1] = isGlobal
724 					// stack[0] = value
725 					boolean isGlobal = position.boolArg(1);
726 					Object o1 = runtimeStack.getVariable(position.intArg(0), isGlobal);
727 					if (o1 == null) {
728 						o1 = BLANK;
729 					}
730 					Object o2 = pop();
731 					double d1 = JRT.toDouble(o1);
732 					double d2 = JRT.toDouble(o2);
733 					double ans;
734 					switch (opcode) {
735 					case PLUS_EQ:
736 						ans = d1 + d2;
737 						break;
738 					case MINUS_EQ:
739 						ans = d1 - d2;
740 						break;
741 					case MULT_EQ:
742 						ans = d1 * d2;
743 						break;
744 					case DIV_EQ:
745 						ans = d1 / d2;
746 						break;
747 					case MOD_EQ:
748 						ans = d1 % d2;
749 						break;
750 					case POW_EQ:
751 						ans = Math.pow(d1, d2);
752 						break;
753 					default:
754 						throw new Error("Invalid opcode here: " + opcode);
755 					}
756 					if (JRT.isActuallyLong(ans)) {
757 						long integral = (long) Math.rint(ans);
758 						push(integral);
759 						runtimeStack.setVariable(position.intArg(0), integral, isGlobal);
760 					} else {
761 						push(ans);
762 						runtimeStack.setVariable(position.intArg(0), ans, isGlobal);
763 					}
764 					position.next();
765 					break;
766 				}
767 				case PLUS_EQ_INPUT_FIELD:
768 				case MINUS_EQ_INPUT_FIELD:
769 				case MULT_EQ_INPUT_FIELD:
770 				case DIV_EQ_INPUT_FIELD:
771 				case MOD_EQ_INPUT_FIELD:
772 				case POW_EQ_INPUT_FIELD: {
773 					// stack[0] = dollar_fieldNumber
774 					// stack[1] = inc value
775 
776 					// same code as GET_INPUT_FIELD:
777 					int fieldnum = parseIntField(pop(), position);
778 					double incval = JRT.toDouble(pop());
779 
780 					// except here, get the number, and add the incvalue
781 					Object numObj = jrt.jrtGetInputField(fieldnum);
782 					double num;
783 					switch (opcode) {
784 					case PLUS_EQ_INPUT_FIELD:
785 						num = JRT.toDouble(numObj) + incval;
786 						break;
787 					case MINUS_EQ_INPUT_FIELD:
788 						num = JRT.toDouble(numObj) - incval;
789 						break;
790 					case MULT_EQ_INPUT_FIELD:
791 						num = JRT.toDouble(numObj) * incval;
792 						break;
793 					case DIV_EQ_INPUT_FIELD:
794 						num = JRT.toDouble(numObj) / incval;
795 						break;
796 					case MOD_EQ_INPUT_FIELD:
797 						num = JRT.toDouble(numObj) % incval;
798 						break;
799 					case POW_EQ_INPUT_FIELD:
800 						num = Math.pow(JRT.toDouble(numObj), incval);
801 						break;
802 					default:
803 						throw new Error("Invalid opcode here: " + opcode);
804 					}
805 					setNumOnJRT(fieldnum, num);
806 
807 					// put the result value on the stack
808 					push(num);
809 					position.next();
810 
811 					break;
812 				}
813 				case INC: {
814 					// arg[0] = offset
815 					// arg[1] = isGlobal
816 					inc(position.intArg(0), position.boolArg(1));
817 					position.next();
818 					break;
819 				}
820 				case DEC: {
821 					// arg[0] = offset
822 					// arg[1] = isGlobal
823 					dec(position.intArg(0), position.boolArg(1));
824 					position.next();
825 					break;
826 				}
827 				case POSTINC: {
828 					// arg[0] = offset
829 					// arg[1] = isGlobal
830 					pop();
831 					push(inc(position.intArg(0), position.boolArg(1)));
832 					position.next();
833 					break;
834 				}
835 				case POSTDEC: {
836 					// arg[0] = offset
837 					// arg[1] = isGlobal
838 					pop();
839 					push(dec(position.intArg(0), position.boolArg(1)));
840 					position.next();
841 					break;
842 				}
843 				case INC_ARRAY_REF: {
844 					// arg[0] = offset
845 					// arg[1] = isGlobal
846 					// stack[0] = array index
847 					boolean isGlobal = position.boolArg(1);
848 					Object o1 = runtimeStack.getVariable(position.intArg(0), isGlobal);
849 					if (o1 == null || o1 instanceof UninitializedObject) {
850 						o1 = new AssocArray(sortedArrayKeys);
851 						runtimeStack.setVariable(position.intArg(0), o1, isGlobal);
852 					}
853 					AssocArray aa = (AssocArray) o1;
854 					Object key = pop();
855 					Object o = aa.get(key);
856 					assert o != null;
857 					double ans = JRT.toDouble(o) + 1;
858 					if (JRT.isActuallyLong(ans)) {
859 						aa.put(key, (long) Math.rint(ans));
860 					} else {
861 						aa.put(key, ans);
862 					}
863 					position.next();
864 					break;
865 				}
866 				case DEC_ARRAY_REF: {
867 					// arg[0] = offset
868 					// arg[1] = isGlobal
869 					// stack[0] = array index
870 					boolean isGlobal = position.boolArg(1);
871 					Object o1 = runtimeStack.getVariable(position.intArg(0), isGlobal);
872 					if (o1 == null || o1 instanceof UninitializedObject) {
873 						o1 = new AssocArray(sortedArrayKeys);
874 						runtimeStack.setVariable(position.intArg(0), o1, isGlobal);
875 					}
876 					AssocArray aa = (AssocArray) o1;
877 					Object key = pop();
878 					Object o = aa.get(key);
879 					assert o != null;
880 					double ans = JRT.toDouble(o) - 1;
881 					if (JRT.isActuallyLong(ans)) {
882 						aa.put(key, (long) Math.rint(ans));
883 					} else {
884 						aa.put(key, ans);
885 					}
886 					position.next();
887 					break;
888 				}
889 				case INC_DOLLAR_REF: {
890 					// stack[0] = dollar index (field number)
891 					int fieldnum = parseIntField(pop(), position);
892 
893 					Object numObj = jrt.jrtGetInputField(fieldnum);
894 					double original = JRT.toDouble(numObj);
895 					double num = original + 1;
896 					setNumOnJRT(fieldnum, num);
897 
898 					if (JRT.isActuallyLong(original)) {
899 						push((long) Math.rint(original));
900 					} else {
901 						push(Double.valueOf(original));
902 					}
903 
904 					position.next();
905 					break;
906 				}
907 				case DEC_DOLLAR_REF: {
908 					// stack[0] = dollar index (field number)
909 					// same code as GET_INPUT_FIELD:
910 					int fieldnum = parseIntField(pop(), position);
911 
912 					Object numObj = jrt.jrtGetInputField(fieldnum);
913 					double original = JRT.toDouble(numObj);
914 					double num = original - 1;
915 					setNumOnJRT(fieldnum, num);
916 
917 					if (JRT.isActuallyLong(original)) {
918 						push((long) Math.rint(original));
919 					} else {
920 						push(Double.valueOf(original));
921 					}
922 
923 					position.next();
924 					break;
925 				}
926 				case DEREFERENCE: {
927 					// arg[0] = offset
928 					// arg[1] = isGlobal
929 					boolean isGlobal = position.boolArg(2);
930 					Object o = runtimeStack.getVariable(position.intArg(0), isGlobal);
931 					if (o == null) {
932 						if (position.boolArg(1)) {
933 							// is_array
934 							push(runtimeStack.setVariable(position.intArg(0), new AssocArray(sortedArrayKeys), isGlobal));
935 						} else {
936 							push(runtimeStack.setVariable(position.intArg(0), BLANK, isGlobal));
937 						}
938 					} else {
939 						push(o);
940 					}
941 					position.next();
942 					break;
943 				}
944 				case DEREF_ARRAY: {
945 					// stack[0] = array index
946 					// stack[1] = AssocArray
947 					Object idx = pop(); // idx
948 					Object array = pop(); // map
949 					if (!(array instanceof AssocArray)) {
950 						throw new AwkRuntimeException("Attempting to index a non-associative-array.");
951 					}
952 					Object o = ((AssocArray) array).get(idx);
953 					assert o != null;
954 					push(o);
955 					position.next();
956 					break;
957 				}
958 				case SRAND: {
959 					// arg[0] = numArgs (where 0 = no args, anything else = one argument)
960 					// stack[0] = seed (only if numArgs != 0)
961 					long numArgs = position.intArg(0);
962 					int seed;
963 					if (numArgs == 0) {
964 						// use the time of day for the seed
965 						seed = JRT.timeSeed();
966 					} else {
967 						Object o = pop();
968 						if (o instanceof Double) {
969 							seed = ((Double) o).intValue();
970 						} else if (o instanceof Long) {
971 							seed = ((Long) o).intValue();
972 						} else if (o instanceof Integer) {
973 							seed = ((Integer) o).intValue();
974 						} else {
975 							try {
976 								seed = Integer.parseInt(o.toString());
977 							} catch (NumberFormatException nfe) {
978 								seed = 0;
979 							}
980 						}
981 					}
982 					randomNumberGenerator.setSeed(seed);
983 					push(oldseed);
984 					oldseed = seed;
985 					position.next();
986 					break;
987 				}
988 				case RAND: {
989 					push(randomNumberGenerator.nextDouble());
990 					position.next();
991 					break;
992 				}
993 				case INTFUNC: {
994 					// stack[0] = arg to int() function
995 					push((long) JRT.toDouble(pop()));
996 					position.next();
997 					break;
998 				}
999 				case SQRT: {
1000 					// stack[0] = arg to sqrt() function
1001 					push(Math.sqrt(JRT.toDouble(pop())));
1002 					position.next();
1003 					break;
1004 				}
1005 				case LOG: {
1006 					// stack[0] = arg to log() function
1007 					push(Math.log(JRT.toDouble(pop())));
1008 					position.next();
1009 					break;
1010 				}
1011 				case EXP: {
1012 					// stack[0] = arg to exp() function
1013 					push(Math.exp(JRT.toDouble(pop())));
1014 					position.next();
1015 					break;
1016 				}
1017 				case SIN: {
1018 					// stack[0] = arg to sin() function
1019 					push(Math.sin(JRT.toDouble(pop())));
1020 					position.next();
1021 					break;
1022 				}
1023 				case COS: {
1024 					// stack[0] = arg to cos() function
1025 					push(Math.cos(JRT.toDouble(pop())));
1026 					position.next();
1027 					break;
1028 				}
1029 				case ATAN2: {
1030 					// stack[0] = 2nd arg to atan2() function
1031 					// stack[1] = 1st arg to atan2() function
1032 					double d2 = JRT.toDouble(pop());
1033 					double d1 = JRT.toDouble(pop());
1034 					push(Math.atan2(d1, d2));
1035 					position.next();
1036 					break;
1037 				}
1038 				case MATCH: {
1039 					// stack[0] = 2nd arg to match() function
1040 					// stack[1] = 1st arg to match() function
1041 					String convfmt = getCONVFMT().toString();
1042 					String ere = JRT.toAwkString(pop(), convfmt, locale);
1043 					String s = JRT.toAwkString(pop(), convfmt, locale);
1044 
1045 					// check if IGNORECASE set
1046 					int flags = 0;
1047 
1048 					if (globalVariableOffsets.containsKey("IGNORECASE")) {
1049 						Integer offsetObj = globalVariableOffsets.get("IGNORECASE");
1050 						Object ignorecase = runtimeStack.getVariable(offsetObj, true);
1051 
1052 						if (JRT.toDouble(ignorecase) != 0) {
1053 							flags |= Pattern.CASE_INSENSITIVE;
1054 						}
1055 					}
1056 
1057 					Pattern pattern = Pattern.compile(ere, flags);
1058 					Matcher matcher = pattern.matcher(s);
1059 					boolean result = matcher.find();
1060 					if (result) {
1061 						assign(rstartOffset, matcher.start() + 1, true, position);
1062 						assign(rlengthOffset, matcher.end() - matcher.start(), true, position);
1063 						pop();
1064 						// end up with RSTART on the stack
1065 					} else {
1066 						assign(rstartOffset, ZERO, true, position);
1067 						assign(rlengthOffset, -1, true, position);
1068 						pop();
1069 						// end up with RSTART on the stack
1070 					}
1071 					position.next();
1072 					break;
1073 				}
1074 				case INDEX: {
1075 					// stack[0] = 2nd arg to index() function
1076 					// stack[1] = 1st arg to index() function
1077 					String convfmt = getCONVFMT().toString();
1078 					String s2 = JRT.toAwkString(pop(), convfmt, locale);
1079 					String s1 = JRT.toAwkString(pop(), convfmt, locale);
1080 					push(s1.indexOf(s2) + 1);
1081 					position.next();
1082 					break;
1083 				}
1084 				case SUB_FOR_DOLLAR_0: {
1085 					// arg[0] = isGlobal
1086 					// stack[0] = replacement string
1087 					// stack[1] = ere
1088 					boolean isGsub = position.boolArg(0);
1089 					String convfmt = getCONVFMT().toString();
1090 					String repl = JRT.toAwkString(pop(), convfmt, locale);
1091 					String ere = JRT.toAwkString(pop(), convfmt, locale);
1092 					String orig = JRT.toAwkString(jrt.jrtGetInputField(0), convfmt, locale);
1093 					String newstring;
1094 					if (isGsub) {
1095 						newstring = replaceAll(orig, ere, repl);
1096 					} else {
1097 						newstring = replaceFirst(orig, ere, repl);
1098 					}
1099 					// assign it to "$0"
1100 					jrt.setInputLine(newstring);
1101 					jrt.jrtParseFields();
1102 					position.next();
1103 					break;
1104 				}
1105 				case SUB_FOR_DOLLAR_REFERENCE: {
1106 					// arg[0] = isGlobal
1107 					// stack[0] = field num
1108 					// stack[1] = original field value
1109 					// stack[2] = replacement string
1110 					// stack[3] = ere
1111 					boolean isGsub = position.boolArg(0);
1112 					String convfmt = getCONVFMT().toString();
1113 					int fieldNum = (int) JRT.toDouble(pop());
1114 					String orig = JRT.toAwkString(pop(), convfmt, locale);
1115 					String repl = JRT.toAwkString(pop(), convfmt, locale);
1116 					String ere = JRT.toAwkString(pop(), convfmt, locale);
1117 					String newstring;
1118 					if (isGsub) {
1119 						newstring = replaceAll(orig, ere, repl);
1120 					} else {
1121 						newstring = replaceFirst(orig, ere, repl);
1122 					}
1123 					// assign it to "$0"
1124 					if (fieldNum == 0) {
1125 						jrt.setInputLine(newstring);
1126 						jrt.jrtParseFields();
1127 					} else {
1128 						jrt.jrtSetInputField(newstring, fieldNum);
1129 					}
1130 					position.next();
1131 					break;
1132 				}
1133 				case SUB_FOR_VARIABLE: {
1134 					// arg[0] = offset
1135 					// arg[1] = isGlobal
1136 					// arg[2] = isGsub
1137 					// stack[0] = original variable value
1138 					// stack[1] = replacement string
1139 					// stack[2] = ere
1140 					long offset = position.intArg(0);
1141 					boolean isGlobal = position.boolArg(1);
1142 					String newString = execSubOrGSub(position, 2);
1143 					// assign it to "offset/global"
1144 					assign(offset, newString, isGlobal, position);
1145 					pop();
1146 					position.next();
1147 					break;
1148 				}
1149 				case SUB_FOR_ARRAY_REFERENCE: {
1150 					// arg[0] = offset
1151 					// arg[1] = isGlobal
1152 					// arg[2] = isGsub
1153 					// stack[0] = original variable value
1154 					// stack[1] = replacement string
1155 					// stack[2] = ere
1156 					// stack[3] = array index
1157 					// ARRAY reference offset/isGlobal
1158 					long offset = position.intArg(0);
1159 					boolean isGlobal = position.boolArg(1);
1160 					Object arrIdx = pop();
1161 					String newString = execSubOrGSub(position, 2);
1162 					// assign it to "offset/arrIdx/global"
1163 					assignArray(offset, arrIdx, newString, isGlobal);
1164 					pop();
1165 					position.next();
1166 					break;
1167 				}
1168 				case SPLIT: {
1169 					// arg[0] = num args
1170 					// stack[0] = field_sep (only if num args == 3)
1171 					// stack[1] = array
1172 					// stack[2] = string
1173 					String convfmt = getCONVFMT().toString();
1174 					long numArgs = position.intArg(0);
1175 					String fsString;
1176 					if (numArgs == 2) {
1177 						fsString = JRT.toAwkString(getFS(), convfmt, locale);
1178 					} else if (numArgs == 3) {
1179 						fsString = JRT.toAwkString(pop(), convfmt, locale);
1180 					} else {
1181 						throw new Error("Invalid # of args. split() requires 2 or 3. Got: " + numArgs);
1182 					}
1183 					Object o = pop();
1184 					if (!(o instanceof AssocArray)) {
1185 						throw new AwkRuntimeException(position.lineNumber(), o + " is not an array.");
1186 					}
1187 					String s = JRT.toAwkString(pop(), convfmt, locale);
1188 					Enumeration<Object> tokenizer;
1189 					if (fsString.equals(" ")) {
1190 						tokenizer = new StringTokenizer(s);
1191 					} else if (fsString.length() == 1) {
1192 						tokenizer = new SingleCharacterTokenizer(s, fsString.charAt(0));
1193 					} else if (fsString.isEmpty()) {
1194 						tokenizer = new CharacterTokenizer(s);
1195 					} else {
1196 						tokenizer = new RegexTokenizer(s, fsString);
1197 					}
1198 
1199 					AssocArray assocArray = (AssocArray) o;
1200 					assocArray.clear();
1201 					int cnt = 0;
1202 					while (tokenizer.hasMoreElements()) {
1203 						assocArray.put(++cnt, tokenizer.nextElement());
1204 					}
1205 					push(cnt);
1206 					position.next();
1207 					break;
1208 				}
1209 				case SUBSTR: {
1210 					// arg[0] = num args
1211 					// stack[0] = length (only if num args == 3)
1212 					// stack[1] = start pos
1213 					// stack[2] = string
1214 					long numArgs = position.intArg(0);
1215 					int startPos, length;
1216 					String s;
1217 					if (numArgs == 3) {
1218 						length = (int) JRT.toLong(pop());
1219 						startPos = (int) JRT.toDouble(pop());
1220 						s = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
1221 					} else if (numArgs == 2) {
1222 						startPos = (int) JRT.toDouble(pop());
1223 						s = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
1224 						length = s.length() - startPos + 1;
1225 					} else {
1226 						throw new Error("numArgs for SUBSTR must be 2 or 3. It is " + numArgs);
1227 					}
1228 					if (startPos <= 0) {
1229 						startPos = 1;
1230 					}
1231 					if (length <= 0 || startPos > s.length()) {
1232 						push(BLANK);
1233 					} else {
1234 						if (startPos + length > s.length()) {
1235 							push(s.substring(startPos - 1));
1236 						} else {
1237 							push(s.substring(startPos - 1, startPos + length - 1));
1238 						}
1239 					}
1240 					position.next();
1241 					break;
1242 				}
1243 				case TOLOWER: {
1244 					// stack[0] = string
1245 					push(JRT.toAwkString(pop(), getCONVFMT().toString(), locale).toLowerCase());
1246 					position.next();
1247 					break;
1248 				}
1249 				case TOUPPER: {
1250 					// stack[0] = string
1251 					push(JRT.toAwkString(pop(), getCONVFMT().toString(), locale).toUpperCase());
1252 					position.next();
1253 					break;
1254 				}
1255 				case SYSTEM: {
1256 // stack[0] = command string
1257 					String s = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
1258 					push(jrt.jrtSystem(s));
1259 					position.next();
1260 					break;
1261 				}
1262 				case SWAP: {
1263 					// stack[0] = item1
1264 					// stack[1] = item2
1265 					swapOnStack();
1266 					position.next();
1267 					break;
1268 				}
1269 				case CMP_EQ: {
1270 					// stack[0] = item2
1271 					// stack[1] = item1
1272 					Object o2 = pop();
1273 					Object o1 = pop();
1274 					push(JRT.compare2(o1, o2, 0) ? ONE : ZERO);
1275 					position.next();
1276 					break;
1277 				}
1278 				case CMP_LT: {
1279 					// stack[0] = item2
1280 					// stack[1] = item1
1281 					Object o2 = pop();
1282 					Object o1 = pop();
1283 					push(JRT.compare2(o1, o2, -1) ? ONE : ZERO);
1284 					position.next();
1285 					break;
1286 				}
1287 				case CMP_GT: {
1288 					// stack[0] = item2
1289 					// stack[1] = item1
1290 					Object o2 = pop();
1291 					Object o1 = pop();
1292 					push(JRT.compare2(o1, o2, 1) ? ONE : ZERO);
1293 					position.next();
1294 					break;
1295 				}
1296 				case MATCHES: {
1297 					// stack[0] = item2
1298 					// stack[1] = item1
1299 					Object o2 = pop();
1300 					Object o1 = pop();
1301 					// use o1's string value
1302 					String s = o1.toString();
1303 					// assume o2 is a regexp
1304 					if (o2 instanceof Pattern) {
1305 						Pattern p = (Pattern) o2;
1306 						Matcher m = p.matcher(s);
1307 						// m.matches() matches the ENTIRE string
1308 						// m.find() is more appropriate
1309 						boolean result = m.find();
1310 						push(result ? 1 : 0);
1311 					} else {
1312 						String r = JRT.toAwkString(o2, getCONVFMT().toString(), locale);
1313 						boolean result = Pattern.compile(r).matcher(s).find();
1314 						push(result ? 1 : 0);
1315 					}
1316 					position.next();
1317 					break;
1318 				}
1319 				case ADD: {
1320 					// stack[0] = item2
1321 					// stack[1] = item1
1322 					Object o2 = pop();
1323 					Object o1 = pop();
1324 					double d1 = JRT.toDouble(o1);
1325 					double d2 = JRT.toDouble(o2);
1326 					double ans = d1 + d2;
1327 					if (JRT.isActuallyLong(ans)) {
1328 						push((long) Math.rint(ans));
1329 					} else {
1330 						push(ans);
1331 					}
1332 					position.next();
1333 					break;
1334 				}
1335 				case SUBTRACT: {
1336 					// stack[0] = item2
1337 					// stack[1] = item1
1338 					Object o2 = pop();
1339 					Object o1 = pop();
1340 					double d1 = JRT.toDouble(o1);
1341 					double d2 = JRT.toDouble(o2);
1342 					double ans = d1 - d2;
1343 					if (JRT.isActuallyLong(ans)) {
1344 						push((long) Math.rint(ans));
1345 					} else {
1346 						push(ans);
1347 					}
1348 					position.next();
1349 					break;
1350 				}
1351 				case MULTIPLY: {
1352 					// stack[0] = item2
1353 					// stack[1] = item1
1354 					Object o2 = pop();
1355 					Object o1 = pop();
1356 					double d1 = JRT.toDouble(o1);
1357 					double d2 = JRT.toDouble(o2);
1358 					double ans = d1 * d2;
1359 					if (JRT.isActuallyLong(ans)) {
1360 						push((long) Math.rint(ans));
1361 					} else {
1362 						push(ans);
1363 					}
1364 					position.next();
1365 					break;
1366 				}
1367 				case DIVIDE: {
1368 					// stack[0] = item2
1369 					// stack[1] = item1
1370 					Object o2 = pop();
1371 					Object o1 = pop();
1372 					double d1 = JRT.toDouble(o1);
1373 					double d2 = JRT.toDouble(o2);
1374 					double ans = d1 / d2;
1375 					if (JRT.isActuallyLong(ans)) {
1376 						push((long) Math.rint(ans));
1377 					} else {
1378 						push(ans);
1379 					}
1380 					position.next();
1381 					break;
1382 				}
1383 				case MOD: {
1384 					// stack[0] = item2
1385 					// stack[1] = item1
1386 					Object o2 = pop();
1387 					Object o1 = pop();
1388 					double d1 = JRT.toDouble(o1);
1389 					double d2 = JRT.toDouble(o2);
1390 					double ans = d1 % d2;
1391 					if (JRT.isActuallyLong(ans)) {
1392 						push((long) Math.rint(ans));
1393 					} else {
1394 						push(ans);
1395 					}
1396 					position.next();
1397 					break;
1398 				}
1399 				case POW: {
1400 					// stack[0] = item2
1401 					// stack[1] = item1
1402 					Object o2 = pop();
1403 					Object o1 = pop();
1404 					double d1 = JRT.toDouble(o1);
1405 					double d2 = JRT.toDouble(o2);
1406 					double ans = Math.pow(d1, d2);
1407 					if (JRT.isActuallyLong(ans)) {
1408 						push((long) Math.rint(ans));
1409 					} else {
1410 						push(ans);
1411 					}
1412 					position.next();
1413 					break;
1414 				}
1415 				case DUP: {
1416 					// stack[0] = top of stack item
1417 					Object o = pop();
1418 					push(o);
1419 					push(o);
1420 					position.next();
1421 					break;
1422 				}
1423 				case KEYLIST: {
1424 					// stack[0] = AssocArray
1425 					Object o = pop();
1426 					assert o != null;
1427 					if (!(o instanceof AssocArray)) {
1428 						throw new AwkRuntimeException(
1429 								position.lineNumber(),
1430 								"Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
1431 					}
1432 					AssocArray aa = (AssocArray) o;
1433 					push(new ArrayDeque<>(aa.keySet()));
1434 					position.next();
1435 					break;
1436 				}
1437 				case IS_EMPTY_KEYLIST: {
1438 					// arg[0] = address
1439 					// stack[0] = Deque
1440 					Object o = pop();
1441 					if (o == null || !(o instanceof Deque)) {
1442 						throw new AwkRuntimeException(
1443 								position.lineNumber(),
1444 								"Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
1445 					}
1446 					Deque<?> keylist = (Deque<?>) o;
1447 					if (keylist.isEmpty()) {
1448 						position.jump(position.addressArg());
1449 					} else {
1450 						position.next();
1451 					}
1452 					break;
1453 				}
1454 				case GET_FIRST_AND_REMOVE_FROM_KEYLIST: {
1455 					// stack[0] = Deque
1456 					Object o = pop();
1457 					if (o == null || !(o instanceof Deque)) {
1458 						throw new AwkRuntimeException(
1459 								position.lineNumber(),
1460 								"Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
1461 					}
1462 					// pop off and return the head of the key set
1463 					Deque<?> keylist = (Deque<?>) o;
1464 					assert !keylist.isEmpty();
1465 					push(keylist.removeFirst());
1466 					position.next();
1467 					break;
1468 				}
1469 				case CHECK_CLASS: {
1470 					// arg[0] = class object
1471 					// stack[0] = item to check
1472 					Object o = pop();
1473 					if (!position.classArg().isInstance(o)) {
1474 						throw new AwkRuntimeException(
1475 								position.lineNumber(),
1476 								"Verification failed. Top-of-stack = " + o.getClass() + " isn't an instance of " + position.classArg());
1477 					}
1478 					push(o);
1479 					position.next();
1480 					break;
1481 				}
1482 				case CONSUME_INPUT: {
1483 					// arg[0] = address
1484 					// false = do NOT put result on stack...
1485 					// instead, put it in field vars ($0, $1, ...)
1486 					if (avmConsumeInput(false)) {
1487 						position.next();
1488 					} else {
1489 						position.jump(position.addressArg());
1490 					}
1491 					break;
1492 				}
1493 
1494 				case SET_INPUT_FOR_EVAL: {
1495 					jrt.setInputLineforEval(settings.getInput());
1496 					position.next();
1497 					break;
1498 				}
1499 
1500 				case GETLINE_INPUT: {
1501 					avmConsumeInputForGetline();
1502 					position.next();
1503 					break;
1504 				}
1505 				case USE_AS_FILE_INPUT: {
1506 // stack[0] = filename
1507 					String s = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
1508 					avmConsumeFileInputForGetline(s);
1509 					position.next();
1510 					break;
1511 				}
1512 				case USE_AS_COMMAND_INPUT: {
1513 // stack[0] = command line
1514 					String s = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
1515 					avmConsumeCommandInputForGetline(s);
1516 					position.next();
1517 					break;
1518 				}
1519 				case NF_OFFSET: {
1520 					// stack[0] = offset
1521 					nfOffset = position.intArg(0);
1522 					assert nfOffset != NULL_OFFSET;
1523 					assign(nfOffset, 0, true, position);
1524 					pop(); // clean up the stack after the assignment
1525 					position.next();
1526 					break;
1527 				}
1528 				case NR_OFFSET: {
1529 					// stack[0] = offset
1530 					nrOffset = position.intArg(0);
1531 					assert nrOffset != NULL_OFFSET;
1532 					assign(nrOffset, 0, true, position);
1533 					pop(); // clean up the stack after the assignment
1534 					position.next();
1535 					break;
1536 				}
1537 				case FNR_OFFSET: {
1538 					// stack[0] = offset
1539 					fnrOffset = position.intArg(0);
1540 					assert fnrOffset != NULL_OFFSET;
1541 					assign(fnrOffset, 0, true, position);
1542 					pop(); // clean up the stack after the assignment
1543 					position.next();
1544 					break;
1545 				}
1546 				case FS_OFFSET: {
1547 					// stack[0] = offset
1548 					fsOffset = position.intArg(0);
1549 					assert fsOffset != NULL_OFFSET;
1550 					if (initialFsValue == null) {
1551 						assign(fsOffset, " ", true, position);
1552 					} else {
1553 						assign(fsOffset, initialFsValue, true, position);
1554 					}
1555 					pop(); // clean up the stack after the assignment
1556 					position.next();
1557 					break;
1558 				}
1559 				case RS_OFFSET: {
1560 					// stack[0] = offset
1561 					rsOffset = position.intArg(0);
1562 					assert rsOffset != NULL_OFFSET;
1563 					assign(rsOffset, settings.getDefaultRS(), true, position);
1564 					pop(); // clean up the stack after the assignment
1565 					position.next();
1566 					break;
1567 				}
1568 				case OFS_OFFSET: {
1569 					// stack[0] = offset
1570 					ofsOffset = position.intArg(0);
1571 					assert ofsOffset != NULL_OFFSET;
1572 					assign(ofsOffset, " ", true, position);
1573 					pop(); // clean up the stack after the assignment
1574 					position.next();
1575 					break;
1576 				}
1577 				case ORS_OFFSET: {
1578 					// stack[0] = offset
1579 					orsOffset = position.intArg(0);
1580 					assert orsOffset != NULL_OFFSET;
1581 					assign(orsOffset, settings.getDefaultORS(), true, position);
1582 					pop(); // clean up the stack after the assignment
1583 					position.next();
1584 					break;
1585 				}
1586 				case RSTART_OFFSET: {
1587 					// stack[0] = offset
1588 					rstartOffset = position.intArg(0);
1589 					assert rstartOffset != NULL_OFFSET;
1590 					assign(rstartOffset, "", true, position);
1591 					pop(); // clean up the stack after the assignment
1592 					position.next();
1593 					break;
1594 				}
1595 				case RLENGTH_OFFSET: {
1596 					// stack[0] = offset
1597 					rlengthOffset = position.intArg(0);
1598 					assert rlengthOffset != NULL_OFFSET;
1599 					assign(rlengthOffset, "", true, position);
1600 					pop(); // clean up the stack after the assignment
1601 					position.next();
1602 					break;
1603 				}
1604 				case FILENAME_OFFSET: {
1605 					// stack[0] = offset
1606 					filenameOffset = position.intArg(0);
1607 					assert filenameOffset != NULL_OFFSET;
1608 					assign(filenameOffset, "", true, position);
1609 					pop(); // clean up the stack after the assignment
1610 					position.next();
1611 					break;
1612 				}
1613 				case SUBSEP_OFFSET: {
1614 					// stack[0] = offset
1615 					subsepOffset = position.intArg(0);
1616 					assert subsepOffset != NULL_OFFSET;
1617 					assign(subsepOffset, String.valueOf((char) 28), true, position);
1618 					pop(); // clean up the stack after the assignment
1619 					position.next();
1620 					break;
1621 				}
1622 				case CONVFMT_OFFSET: {
1623 					// stack[0] = offset
1624 					convfmtOffset = position.intArg(0);
1625 					assert convfmtOffset != NULL_OFFSET;
1626 					assign(convfmtOffset, "%.6g", true, position);
1627 					pop(); // clean up the stack after the assignment
1628 					position.next();
1629 					break;
1630 				}
1631 				case OFMT_OFFSET: {
1632 					// stack[0] = offset
1633 					ofmtOffset = position.intArg(0);
1634 					assert ofmtOffset != NULL_OFFSET;
1635 					assign(ofmtOffset, "%.6g", true, position);
1636 					pop(); // clean up the stack after the assignment
1637 					position.next();
1638 					break;
1639 				}
1640 				case ENVIRON_OFFSET: {
1641 					// stack[0] = offset
1642 					//// assignArray(offset, arrIdx, newstring, isGlobal);
1643 					environOffset = position.intArg(0);
1644 					assert environOffset != NULL_OFFSET;
1645 					// set the initial variables
1646 					Map<String, String> env = System.getenv();
1647 					for (Map.Entry<String, String> var : env.entrySet()) {
1648 						assignArray(environOffset, var.getKey(), var.getValue(), true);
1649 						pop(); // clean up the stack after the assignment
1650 					}
1651 					position.next();
1652 					break;
1653 				}
1654 				case ARGC_OFFSET: {
1655 					// stack[0] = offset
1656 					argcOffset = position.intArg(0);
1657 					assert argcOffset != NULL_OFFSET;
1658 					// assign(argcOffset, arguments.size(), true, position); // true = global
1659 					// +1 to include the "java Awk" (ARGV[0])
1660 					assign(argcOffset, arguments.size() + 1, true, position); // true = global
1661 					pop(); // clean up the stack after the assignment
1662 					position.next();
1663 					break;
1664 				}
1665 				case ARGV_OFFSET: {
1666 					// stack[0] = offset
1667 					argvOffset = position.intArg(0);
1668 					assert argvOffset != NULL_OFFSET;
1669 					// consume argv (looping from 1 to argc)
1670 					int argc = (int) JRT.toDouble(runtimeStack.getVariable(argcOffset, true)); // true = global
1671 					assignArray(argvOffset, 0, "java Awk", true);
1672 					pop();
1673 					for (int i = 1; i < argc; i++) {
1674 						// assignArray(argvOffset, i+1, arguments.get(i), true);
1675 						assignArray(argvOffset, i, arguments.get(i - 1), true);
1676 						pop(); // clean up the stack after the assignment
1677 					}
1678 					position.next();
1679 					break;
1680 				}
1681 				case GET_INPUT_FIELD: {
1682 					// stack[0] = field number
1683 					int fieldnum = parseIntField(pop(), position);
1684 					push(jrt.jrtGetInputField(fieldnum));
1685 					position.next();
1686 					break;
1687 				}
1688 				case APPLY_RS: {
1689 					assert rsOffset != NULL_OFFSET;
1690 					Object rsObj = runtimeStack.getVariable(rsOffset, true); // true = global
1691 					if (jrt.getPartitioningReader() != null) {
1692 						jrt.getPartitioningReader().setRecordSeparator(rsObj.toString());
1693 					}
1694 					position.next();
1695 					break;
1696 				}
1697 				case CALL_FUNCTION: {
1698 					// arg[0] = function address
1699 					// arg[1] = function name
1700 					// arg[2] = # of formal parameters
1701 					// arg[3] = # of actual parameters
1702 					// stack[0] = last actual parameter
1703 					// stack[1] = before-last actual parameter
1704 					// ...
1705 					// stack[n-1] = first actual parameter
1706 					// etc.
1707 					Address funcAddr = position.addressArg();
1708 					// String func_name = position.arg(1).toString();
1709 					long numFormalParams = position.intArg(2);
1710 					long numActualParams = position.intArg(3);
1711 					assert numFormalParams >= numActualParams;
1712 					runtimeStack.pushFrame(numFormalParams, position.current());
1713 					// Arguments are stacked, so first in the stack is the last for the function
1714 					for (long i = numActualParams - 1; i >= 0; i--) {
1715 						runtimeStack.setVariable(i, pop(), false); // false = local
1716 					}
1717 					position.jump(funcAddr);
1718 					// position.next();
1719 					break;
1720 				}
1721 				case FUNCTION: {
1722 					// important for compilation,
1723 					// not needed for interpretation
1724 					// arg[0] = function name
1725 					// arg[1] = # of formal parameters
1726 					position.next();
1727 					break;
1728 				}
1729 				case SET_RETURN_RESULT: {
1730 					// stack[0] = return result
1731 					runtimeStack.setReturnValue(pop());
1732 					position.next();
1733 					break;
1734 				}
1735 				case RETURN_FROM_FUNCTION: {
1736 					position.jump(runtimeStack.popFrame());
1737 					push(runtimeStack.getReturnValue());
1738 					position.next();
1739 					break;
1740 				}
1741 				case SET_NUM_GLOBALS: {
1742 					// arg[0] = # of globals
1743 					assert position.intArg(0) == globalVariableOffsets.size();
1744 					runtimeStack.setNumGlobals(position.intArg(0));
1745 
1746 					// now that we have the global variable size,
1747 					// we can allocate the initial variables
1748 
1749 					// assign -v variables (from initialVariables container)
1750 					for (Map.Entry<String, Object> entry : initialVariables.entrySet()) {
1751 						String key = entry.getKey();
1752 						if (functionNames.contains(key)) {
1753 							throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + key + ").");
1754 						}
1755 						Integer offsetObj = globalVariableOffsets.get(key);
1756 						Boolean arrayObj = globalVariableArrays.get(key);
1757 						if (offsetObj != null) {
1758 							assert arrayObj != null;
1759 							if (arrayObj.booleanValue()) {
1760 								throw new IllegalArgumentException("Cannot assign a scalar to a non-scalar variable (" + key + ").");
1761 							} else {
1762 								Object obj = entry.getValue();
1763 								runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
1764 							}
1765 						}
1766 					}
1767 
1768 					position.next();
1769 					break;
1770 				}
1771 				case CLOSE: {
1772 					// stack[0] = file or command line to close
1773 					String s = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
1774 					push(jrt.jrtClose(s));
1775 					position.next();
1776 					break;
1777 				}
1778 				case APPLY_SUBSEP: {
1779 					// arg[0] = # of elements for SUBSEP application
1780 					// stack[0] = first element
1781 					// stack[1] = second element
1782 					// etc.
1783 					long count = position.intArg(0);
1784 					assert count >= 1;
1785 					// String s;
1786 					String convfmt = getCONVFMT().toString();
1787 					if (count == 1) {
1788 						push(JRT.toAwkString(pop(), convfmt, locale));
1789 					} else {
1790 						StringBuilder sb = new StringBuilder();
1791 						sb.append(JRT.toAwkString(pop(), convfmt, locale));
1792 						String subsep = JRT.toAwkString(runtimeStack.getVariable(subsepOffset, true), convfmt, locale);
1793 						for (int i = 1; i < count; i++) {
1794 							sb.insert(0, subsep);
1795 							sb.insert(0, JRT.toAwkString(pop(), convfmt, locale));
1796 						}
1797 						push(sb.toString());
1798 					}
1799 					position.next();
1800 					break;
1801 				}
1802 				case DELETE_ARRAY_ELEMENT: {
1803 					// arg[0] = offset
1804 					// arg[1] = isGlobal
1805 					// stack[0] = array index
1806 					long offset = position.intArg(0);
1807 					boolean isGlobal = position.boolArg(1);
1808 					AssocArray aa = (AssocArray) runtimeStack.getVariable(offset, isGlobal);
1809 					Object key = pop();
1810 					if (aa != null) {
1811 						aa.remove(key);
1812 					}
1813 					position.next();
1814 					break;
1815 				}
1816 				case DELETE_ARRAY: {
1817 					// arg[0] = offset
1818 					// arg[1] = isGlobal
1819 					// (nothing on the stack)
1820 					long offset = position.intArg(0);
1821 					boolean isGlobal = position.boolArg(1);
1822 					runtimeStack.removeVariable(offset, isGlobal);
1823 					position.next();
1824 					break;
1825 				}
1826 				case SET_EXIT_ADDRESS: {
1827 					// arg[0] = exit address
1828 					exitAddress = position.addressArg();
1829 					position.next();
1830 					break;
1831 				}
1832 				case SET_WITHIN_END_BLOCKS: {
1833 					// arg[0] = whether within the END blocks section
1834 					withinEndBlocks = position.boolArg(0);
1835 					position.next();
1836 					break;
1837 				}
1838 				case EXIT_WITHOUT_CODE:
1839 				case EXIT_WITH_CODE: {
1840 					if (opcode == Opcode.EXIT_WITH_CODE) {
1841 						// stack[0] = exit code
1842 						exitCode = (int) JRT.toDouble(pop());
1843 					}
1844 					throwExitException = true;
1845 
1846 					// If in BEGIN or in a rule, jump to the END section
1847 					if (!withinEndBlocks && exitAddress != null) {
1848 						// clear runtime stack
1849 						runtimeStack.popAllFrames();
1850 						// clear operand stack
1851 						operandStack.clear();
1852 						position.jump(exitAddress);
1853 					} else {
1854 						// Exit immediately with ExitException
1855 						jrt.jrtCloseAll();
1856 						// clear operand stack
1857 						operandStack.clear();
1858 						throw new ExitException(exitCode, "The AWK script requested an exit");
1859 						// position.next();
1860 					}
1861 					break;
1862 				}
1863 				case REGEXP: {
1864 					// arg[0] = string representation of regexp
1865 					String key = JRT.toAwkString(position.arg(0), getCONVFMT().toString(), locale);
1866 					Pattern pattern = regexps.get(key);
1867 					if (pattern == null) {
1868 						pattern = Pattern.compile(key);
1869 						regexps.put(key, pattern);
1870 					}
1871 					push(pattern);
1872 					position.next();
1873 					break;
1874 				}
1875 				case CONDITION_PAIR: {
1876 					// stack[0] = End condition
1877 					// stack[1] = Start condition
1878 					ConditionPair cp = conditionPairs.get(position.current());
1879 					if (cp == null) {
1880 						cp = new ConditionPair();
1881 						conditionPairs.put(position.current(), cp);
1882 					}
1883 					boolean end = jrt.toBoolean(pop());
1884 					boolean start = jrt.toBoolean(pop());
1885 					push(cp.update(start, end) ? ONE : ZERO);
1886 					position.next();
1887 					break;
1888 				}
1889 				case IS_IN: {
1890 					// stack[0] = AssocArray
1891 					// stack[1] = key to check
1892 					Object arr = pop();
1893 					Object arg = pop();
1894 					AssocArray aa = (AssocArray) arr;
1895 					boolean result = aa.isIn(arg);
1896 					push(result ? ONE : ZERO);
1897 					position.next();
1898 					break;
1899 				}
1900 				case THIS: {
1901 					// this is in preparation for a function
1902 					// call for the JVM-COMPILED script, only
1903 					// therefore, do NOTHING for the interpreted
1904 					// version
1905 					position.next();
1906 					break;
1907 				}
1908 				case EXEC: {
1909 					// stack[0] = Jawk code
1910 
1911 					// Experimental feature. Use with caution.
1912 					String awkCode = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
1913 					List<ScriptSource> scriptSources = new ArrayList<ScriptSource>(1);
1914 					scriptSources
1915 							.add(new ScriptSource(ScriptSource.DESCRIPTION_COMMAND_LINE_SCRIPT, new StringReader(awkCode)));
1916 
1917 					org.metricshub.jawk.frontend.AwkParser ap = new org.metricshub.jawk.frontend.AwkParser(
1918 							extensionFunctions);
1919 					try {
1920 						AstNode ast = ap.parse(scriptSources);
1921 						if (ast != null) {
1922 							ast.semanticAnalysis();
1923 							ast.semanticAnalysis();
1924 							AwkTuples newTuples = createTuples();
1925 							int result = ast.populateTuples(newTuples);
1926 							assert result == 0;
1927 							newTuples.postProcess();
1928 							ap.populateGlobalVariableNameToOffsetMappings(newTuples);
1929 							AVM newAvm = createSubAvm(
1930 									settings,
1931 									extensionInstances,
1932 									extensionFunctions);
1933 							int subScriptExitCode = 0;
1934 							try {
1935 								newAvm.interpret(newTuples);
1936 							} catch (ExitException ex) {
1937 								subScriptExitCode = ex.getCode();
1938 							}
1939 							push(subScriptExitCode);
1940 						} else {
1941 							push(-1);
1942 						}
1943 					} catch (IOException ioe) {
1944 						throw new AwkRuntimeException(position.lineNumber(), "IO Exception caught : " + ioe);
1945 					}
1946 
1947 					position.next();
1948 					break;
1949 				}
1950 				case EXTENSION: {
1951 					// arg[0] = extension function metadata
1952 					// arg[1] = # of args on the stack
1953 					// arg[2] = true if parent is NOT an extension function call
1954 					// (i.e., initial extension in calling expression)
1955 					// stack[0] = first actual parameter
1956 					// stack[1] = second actual parameter
1957 					// etc.
1958 					ExtensionFunction function = position.extensionFunctionArg();
1959 					long numArgs = position.intArg(1);
1960 					boolean isInitial = position.boolArg(2);
1961 
1962 					Object[] args = new Object[(int) numArgs];
1963 					for (int i = (int) numArgs - 1; i >= 0; i--) {
1964 						args[i] = pop();
1965 					}
1966 
1967 					String extensionClassName = function.getExtensionClassName();
1968 					JawkExtension extension = extensionInstances.get(extensionClassName);
1969 					if (extension == null) {
1970 						throw new AwkRuntimeException(
1971 								position.lineNumber(),
1972 								"Extension instance for class '" + extensionClassName
1973 										+ "' is not registered");
1974 					}
1975 					if (!(extension instanceof AbstractExtension)) {
1976 						throw new AwkRuntimeException(
1977 								position.lineNumber(),
1978 								"Extension instance for class '" + extensionClassName
1979 										+ "' does not extend "
1980 										+ AbstractExtension.class.getName());
1981 					}
1982 
1983 					Object retval = function.invoke((AbstractExtension) extension, args);
1984 
1985 					// block if necessary
1986 					// (convert retval into the return value
1987 					// from the block operation ...)
1988 					if (isInitial && retval != null && retval instanceof BlockObject) {
1989 						retval = new BlockManager().block((BlockObject) retval);
1990 					}
1991 					// (... and proceed)
1992 
1993 					if (retval == null) {
1994 						retval = "";
1995 					} else
1996 						if (!(retval instanceof Integer
1997 								||
1998 								retval instanceof Long
1999 								||
2000 								retval instanceof Double
2001 								||
2002 								retval instanceof String
2003 								||
2004 								retval instanceof AssocArray
2005 								||
2006 								retval instanceof BlockObject)) {
2007 									// all other extension results are converted
2008 									// to a string (via Object.toString())
2009 									retval = retval.toString();
2010 								}
2011 					push(retval);
2012 
2013 					position.next();
2014 					break;
2015 				}
2016 				default:
2017 					throw new Error("invalid opcode: " + position.opcode());
2018 				}
2019 			}
2020 
2021 			// End of the instructions
2022 			jrt.jrtCloseAll();
2023 		} catch (RuntimeException re) {
2024 // clear runtime stack
2025 			runtimeStack.popAllFrames();
2026 // clear operand stack
2027 			operandStack.clear();
2028 			if (re instanceof AwkSandboxException) {
2029 				throw re;
2030 			}
2031 			throw new AwkRuntimeException(position.lineNumber(), re.getMessage(), re);
2032 		} catch (AssertionError ae) {
2033 // clear runtime stack
2034 			runtimeStack.popAllFrames();
2035 // clear operand stack
2036 			operandStack.clear();
2037 			throw ae;
2038 		}
2039 
2040 		// If <code>exit</code> was called, throw an ExitException
2041 		if (throwExitException) {
2042 			throw new ExitException(exitCode, "The AWK script requested an exit");
2043 		}
2044 	}
2045 
2046 	/**
2047 	 * Close all streams in the runtime
2048 	 */
2049 	public void waitForIO() {
2050 		jrt.jrtCloseAll();
2051 	}
2052 
2053 	private void printTo(PrintStream ps, long numArgs) {
2054 		// print items from the top of the stack
2055 		// # of items
2056 		if (numArgs == 0) {
2057 			// display $0
2058 			ps.print(jrt.jrtGetInputField(0));
2059 			ps.print(getORS().toString());
2060 		} else {
2061 			// cache $OFS to separate fields below
2062 			// (no need to execute getOFS for each field)
2063 			String ofsString = getOFS().toString();
2064 
2065 			// Arguments are stacked, so we need to reverse order
2066 			Object[] args = new Object[(int) numArgs];
2067 			for (int i = (int) numArgs - 1; i >= 0; i--) {
2068 				args[i] = pop();
2069 			}
2070 
2071 			// Now print
2072 			for (int i = 0; i < numArgs; i++) {
2073 				ps.print(JRT.toAwkStringForOutput(args[i], getOFMT().toString(), locale));
2074 				// if more elements, display $FS
2075 				if (i < numArgs - 1) {
2076 					// use $OFS to separate fields
2077 					ps.print(ofsString);
2078 				}
2079 			}
2080 			ps.print(getORS().toString());
2081 		}
2082 		// always flush to ensure ORS is written even when it does not
2083 		// contain a newline character
2084 		ps.flush();
2085 	}
2086 
2087 	private void printfTo(PrintStream ps, long numArgs) {
2088 		// assert numArgs > 0;
2089 		ps.print(sprintfFunction(numArgs));
2090 		// for now, since we are not using Process.waitFor()
2091 		if (IS_WINDOWS) {
2092 			ps.flush();
2093 		}
2094 	}
2095 
2096 	/**
2097 	 * sprintf() functionality
2098 	 */
2099 	private String sprintfFunction(long numArgs) {
2100 		// Silly case
2101 		if (numArgs == 0) {
2102 			return "";
2103 		}
2104 
2105 		// all but the format argument
2106 		Object[] argArray = new Object[(int) (numArgs - 1)];
2107 
2108 		// for each sprintf argument, put it into an
2109 		// array used in the String.format method
2110 		// Arguments are stacked, so we need to reverse their order
2111 		for (int i = (int) numArgs - 2; i >= 0; i--) {
2112 			argArray[i] = pop();
2113 		}
2114 
2115 		// the format argument!
2116 		String fmt = JRT.toAwkString(pop(), getCONVFMT().toString(), locale);
2117 
2118 		if (trapIllegalFormatExceptions) {
2119 			return Printf4J.sprintf(locale, fmt, argArray);
2120 		} else {
2121 			return JRT.sprintfNoCatch(locale, fmt, argArray);
2122 		}
2123 	}
2124 
2125 	private StringBuffer replaceFirstSb = new StringBuffer();
2126 
2127 	/**
2128 	 * sub() functionality
2129 	 */
2130 	private String replaceFirst(String orig, String ere, String repl) {
2131 		push(JRT.replaceFirst(orig, repl, ere, replaceFirstSb));
2132 		return replaceFirstSb.toString();
2133 	}
2134 
2135 	private StringBuffer replaceAllSb = new StringBuffer();
2136 
2137 	/**
2138 	 * gsub() functionality
2139 	 */
2140 	private String replaceAll(String orig, String ere, String repl) {
2141 		push(JRT.replaceAll(orig, repl, ere, replaceAllSb));
2142 		return replaceAllSb.toString();
2143 	}
2144 
2145 	/**
2146 	 * Awk variable assignment functionality.
2147 	 */
2148 	private void assign(long l, Object value, boolean isGlobal, PositionTracker position) {
2149 		// check if curr value already refers to an array
2150 		if (runtimeStack.getVariable(l, isGlobal) instanceof AssocArray) {
2151 			throw new AwkRuntimeException(position.lineNumber(), "cannot assign anything to an unindexed associative array");
2152 		}
2153 		push(value);
2154 		runtimeStack.setVariable(l, value, isGlobal);
2155 		if (l == nfOffset && jrt != null && jrt.hasInputFields()) {
2156 			jrt.jrtSetNF(value);
2157 		}
2158 	}
2159 
2160 	/**
2161 	 * Awk array element assignment functionality.
2162 	 */
2163 	private void assignArray(long offset, Object arrIdx, Object rhs, boolean isGlobal) {
2164 		Object o1 = runtimeStack.getVariable(offset, isGlobal);
2165 		if (o1 == null || o1.equals(BLANK)) {
2166 			o1 = new AssocArray(sortedArrayKeys);
2167 			runtimeStack.setVariable(offset, o1, isGlobal);
2168 		}
2169 		assert o1 != null;
2170 		// The only (conceivable) way to contradict
2171 		// the assertion (below) is by passing in
2172 		// a scalar to an unindexed associative array
2173 		// via a -v argument without safeguards to
2174 		// prohibit this.
2175 		// Therefore, guard against this elsewhere, not here.
2176 		// if (! (o1 instanceof AssocArray))
2177 		// throw new AwkRuntimeException("Attempting to treat a scalar as an array.");
2178 		assert o1 instanceof AssocArray;
2179 		AssocArray array = (AssocArray) o1;
2180 
2181 		// Convert arrIdx to a true integer if it is one
2182 		// String indexString = JRT.toAwkStringForOutput(arrIdx, getCONVFMT().toString());
2183 		array.put(arrIdx, rhs);
2184 		push(rhs);
2185 	}
2186 
2187 	/**
2188 	 * Numerically increases an Awk variable by one; the result
2189 	 * is placed back into that variable.
2190 	 */
2191 	private Object inc(long l, boolean isGlobal) {
2192 		Object o = runtimeStack.getVariable(l, isGlobal);
2193 		if (o == null || o instanceof UninitializedObject) {
2194 			o = ZERO;
2195 			runtimeStack.setVariable(l, o, isGlobal);
2196 		}
2197 		runtimeStack.setVariable(l, JRT.inc(o), isGlobal);
2198 		return o;
2199 	}
2200 
2201 	/**
2202 	 * Numerically decreases an Awk variable by one; the result
2203 	 * is placed back into that variable.
2204 	 */
2205 	private Object dec(long l, boolean isGlobal) {
2206 		Object o = runtimeStack.getVariable(l, isGlobal);
2207 		if (o == null || o instanceof UninitializedObject) {
2208 			o = ZERO;
2209 			runtimeStack.setVariable(l, o, isGlobal);
2210 		}
2211 		runtimeStack.setVariable(l, JRT.dec(o), isGlobal);
2212 		return o;
2213 	}
2214 
2215 	/** {@inheritDoc} */
2216 	@Override
2217 	public final Object getRS() {
2218 		assert rsOffset != NULL_OFFSET;
2219 		Object rsObj = runtimeStack.getVariable(rsOffset, true); // true = global
2220 		return rsObj;
2221 	}
2222 
2223 	/** {@inheritDoc} */
2224 	@Override
2225 	public final Object getOFS() {
2226 		assert ofsOffset != NULL_OFFSET;
2227 		Object ofsObj = runtimeStack.getVariable(ofsOffset, true); // true = global
2228 		return ofsObj;
2229 	}
2230 
2231 	public final Object getORS() {
2232 		return runtimeStack.getVariable(orsOffset, true); // true = global
2233 	}
2234 
2235 	/** {@inheritDoc} */
2236 	@Override
2237 	public final Object getSUBSEP() {
2238 		assert subsepOffset != NULL_OFFSET;
2239 		Object subsepObj = runtimeStack.getVariable(subsepOffset, true); // true = global
2240 		return subsepObj;
2241 	}
2242 
2243 	/**
2244 	 * Performs the global variable assignment within the runtime environment.
2245 	 * These assignments come from the ARGV list (bounded by ARGC), which, in
2246 	 * turn, come from the command-line arguments passed into Awk.
2247 	 *
2248 	 * @param nameValue The variable assignment in <i>name=value</i> form.
2249 	 */
2250 	@SuppressWarnings("unused")
2251 	private void setFilelistVariable(String nameValue) {
2252 		int eqIdx = nameValue.indexOf('=');
2253 		// variable name should be non-blank
2254 		assert eqIdx >= 0;
2255 		if (eqIdx == 0) {
2256 			throw new IllegalArgumentException(
2257 					"Must have a non-blank variable name in a name=value variable assignment argument.");
2258 		}
2259 		String name = nameValue.substring(0, eqIdx);
2260 		String value = nameValue.substring(eqIdx + 1);
2261 		Object obj;
2262 		try {
2263 			obj = Integer.parseInt(value);
2264 		} catch (NumberFormatException nfe) {
2265 			try {
2266 				obj = Double.parseDouble(value);
2267 			} catch (NumberFormatException nfe2) {
2268 				obj = value;
2269 			}
2270 		}
2271 
2272 		// make sure we're not receiving funcname=value assignments
2273 		if (functionNames.contains(name)) {
2274 			throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + name + ").");
2275 		}
2276 
2277 		Integer offsetObj = globalVariableOffsets.get(name);
2278 		Boolean arrayObj = globalVariableArrays.get(name);
2279 
2280 		if (offsetObj != null) {
2281 			assert arrayObj != null;
2282 			if (arrayObj.booleanValue()) {
2283 				throw new IllegalArgumentException("Cannot assign a scalar to a non-scalar variable (" + name + ").");
2284 			} else {
2285 				runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
2286 			}
2287 		}
2288 		// otherwise, do nothing
2289 	}
2290 
2291 	/** {@inheritDoc} */
2292 	@Override
2293 	public final void assignVariable(String name, Object obj) {
2294 		// make sure we're not receiving funcname=value assignments
2295 		if (functionNames.contains(name)) {
2296 			throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + name + ").");
2297 		}
2298 
2299 		Integer offsetObj = globalVariableOffsets.get(name);
2300 		Boolean arrayObj = globalVariableArrays.get(name);
2301 
2302 		if (offsetObj != null) {
2303 			assert arrayObj != null;
2304 			if (arrayObj.booleanValue()) {
2305 				throw new IllegalArgumentException("Cannot assign a scalar to a non-scalar variable (" + name + ").");
2306 			} else {
2307 				runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
2308 			}
2309 		}
2310 	}
2311 
2312 	private void swapOnStack() {
2313 		Object o1 = pop();
2314 		Object o2 = pop();
2315 		push(o1);
2316 		push(o2);
2317 	}
2318 
2319 	private void avmConsumeInputForGetline() throws IOException {
2320 		if (avmConsumeInput(true)) {
2321 			push(1);
2322 		} else {
2323 			push("");
2324 			push(0);
2325 		}
2326 		swapOnStack();
2327 	}
2328 
2329 	private void avmConsumeFileInputForGetline(String filename) throws IOException {
2330 		if (avmConsumeFileInput(filename)) {
2331 			push(1);
2332 		} else {
2333 			push(0);
2334 		}
2335 		swapOnStack();
2336 	}
2337 
2338 	private void avmConsumeCommandInputForGetline(String cmd) throws IOException {
2339 		if (avmConsumeCommandInput(cmd)) {
2340 			push(1);
2341 		} else {
2342 			push(0);
2343 		}
2344 		swapOnStack();
2345 	}
2346 
2347 	private boolean avmConsumeFileInput(String filename) throws IOException {
2348 		boolean retval = jrt.jrtConsumeFileInput(filename);
2349 		if (retval) {
2350 			push(jrt.getInputLine());
2351 		} else {
2352 			push("");
2353 		}
2354 		return retval;
2355 	}
2356 
2357 	private boolean avmConsumeCommandInput(String cmd) throws IOException {
2358 		boolean retval = jrt.jrtConsumeCommandInput(cmd);
2359 		if (retval) {
2360 			push(jrt.getInputLine());
2361 		} else {
2362 			push("");
2363 		}
2364 		return retval;
2365 	}
2366 
2367 	/**
2368 	 * Consume input from the current source defined in {@link #settings} and push
2369 	 * the line onto the stack when {@code getline} semantics are required.
2370 	 *
2371 	 * @param forGetline {@code true} when called for {@code getline}; otherwise
2372 	 *        fields are parsed immediately and nothing is pushed
2373 	 * @return {@code true} if a line of input was read
2374 	 * @throws IOException if an I/O error occurs while reading input
2375 	 */
2376 	private boolean avmConsumeInput(boolean forGetline) throws IOException {
2377 		boolean retval = jrt.consumeInput(settings.getInput(), forGetline, locale);
2378 		if (retval && forGetline) {
2379 			push(jrt.getInputLine());
2380 		}
2381 		return retval;
2382 	}
2383 
2384 	/** {@inheritDoc} */
2385 	@Override
2386 	public Object getFS() {
2387 		assert fsOffset != NULL_OFFSET;
2388 		Object fsString = runtimeStack.getVariable(fsOffset, true); // true = global
2389 		return fsString;
2390 	}
2391 
2392 	/** {@inheritDoc} */
2393 	@Override
2394 	public Object getCONVFMT() {
2395 		assert convfmtOffset != NULL_OFFSET : "convfmtOffset not defined";
2396 		Object convfmtString = runtimeStack.getVariable(convfmtOffset, true); // true = global
2397 		return convfmtString;
2398 	}
2399 
2400 	/** {@inheritDoc} */
2401 	@Override
2402 	public void resetFNR() {
2403 		runtimeStack.setVariable(fnrOffset, ZERO, true);
2404 	}
2405 
2406 	/** {@inheritDoc} */
2407 	@Override
2408 	public void incFNR() {
2409 		inc(fnrOffset, true);
2410 	}
2411 
2412 	/** {@inheritDoc} */
2413 	@Override
2414 	public void incNR() {
2415 		inc(nrOffset, true);
2416 	}
2417 
2418 	/** {@inheritDoc} */
2419 	@Override
2420 	public void setNF(Integer newNf) {
2421 		runtimeStack.setVariable(nfOffset, newNf, true);
2422 	}
2423 
2424 	/** {@inheritDoc} */
2425 	@Override
2426 	public void setFILENAME(String filename) {
2427 		runtimeStack.setVariable(filenameOffset, filename, true);
2428 	}
2429 
2430 	/** {@inheritDoc} */
2431 	@Override
2432 	public Object getARGV() {
2433 		return runtimeStack.getVariable(argvOffset, true);
2434 	}
2435 
2436 	/** {@inheritDoc} */
2437 	@Override
2438 	public Object getARGC() {
2439 		return runtimeStack.getVariable(argcOffset, true);
2440 	}
2441 
2442 	private String getOFMT() {
2443 		assert ofmtOffset != NULL_OFFSET;
2444 		String ofmtString = runtimeStack.getVariable(ofmtOffset, true).toString(); // true = global
2445 		return ofmtString;
2446 	}
2447 
2448 	private static final UninitializedObject BLANK = new UninitializedObject();
2449 
2450 	/**
2451 	 * The value of an address which is not yet assigned a tuple index.
2452 	 */
2453 	public static final int NULL_OFFSET = -1;
2454 
2455 }