View Javadoc
1   package io.jawk.backend;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * Jawk
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2006 - 2026 MetricsHub
8    * ჻჻჻჻჻჻
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23   */
24  
25  import java.io.Closeable;
26  import java.io.IOException;
27  import java.io.PrintStream;
28  import java.util.Arrays;
29  import java.util.HashSet;
30  import java.util.LinkedHashMap;
31  import java.util.LinkedHashSet;
32  import java.util.Objects;
33  import java.util.ArrayDeque;
34  import java.util.ArrayList;
35  import java.util.Collections;
36  import java.util.Deque;
37  import java.util.Enumeration;
38  import java.util.HashMap;
39  import java.util.List;
40  import java.util.Locale;
41  import java.util.Map;
42  import java.util.Set;
43  import java.util.StringTokenizer;
44  import java.util.regex.Matcher;
45  import java.util.regex.Pattern;
46  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
47  import io.jawk.AwkExpression;
48  import io.jawk.AwkProgram;
49  import io.jawk.AwkSandboxException;
50  import io.jawk.ExitException;
51  import io.jawk.ext.AbstractExtension;
52  import io.jawk.ext.ExtensionFunction;
53  import io.jawk.ext.JawkExtension;
54  import io.jawk.intermediate.Address;
55  import io.jawk.intermediate.Opcode;
56  import io.jawk.intermediate.PositionTracker;
57  import io.jawk.intermediate.Tuple;
58  import io.jawk.intermediate.Tuple.BooleanTuple;
59  import io.jawk.intermediate.Tuple.CallFunctionTuple;
60  import io.jawk.intermediate.Tuple.ClassTuple;
61  import io.jawk.intermediate.Tuple.CountAndAppendTuple;
62  import io.jawk.intermediate.Tuple.CountTuple;
63  import io.jawk.intermediate.Tuple.DereferenceTuple;
64  import io.jawk.intermediate.Tuple.ExtensionTuple;
65  import io.jawk.intermediate.Tuple.InputFieldTuple;
66  import io.jawk.intermediate.Tuple.LongTuple;
67  import io.jawk.intermediate.Tuple.PushDoubleTuple;
68  import io.jawk.intermediate.Tuple.PushLongTuple;
69  import io.jawk.intermediate.Tuple.PushStringTuple;
70  import io.jawk.intermediate.Tuple.RegexTuple;
71  import io.jawk.intermediate.Tuple.SubstitutionVariableTuple;
72  import io.jawk.intermediate.Tuple.VariableTuple;
73  import io.jawk.intermediate.UninitializedObject;
74  import io.jawk.jrt.AssocArray;
75  import io.jawk.jrt.AwkRuntimeException;
76  import io.jawk.jrt.AwkSink;
77  import io.jawk.jrt.BlockManager;
78  import io.jawk.jrt.BlockObject;
79  import io.jawk.jrt.CharacterTokenizer;
80  import io.jawk.jrt.ConditionPair;
81  import io.jawk.jrt.InputSource;
82  import io.jawk.jrt.StreamInputSource;
83  import io.jawk.jrt.JRT;
84  import io.jawk.jrt.RegexTokenizer;
85  import io.jawk.jrt.SingleCharacterTokenizer;
86  import io.jawk.jrt.VariableManager;
87  import io.jawk.util.AwkSettings;
88  import io.jawk.jrt.BSDRandom;
89  
90  /**
91   * The Jawk interpreter.
92   * <p>
93   * It takes tuples constructed by the intermediate step
94   * and executes each tuple in accordance to their instruction semantics.
95   * The tuples correspond to the Awk script compiled by the parser.
96   * The interpreter consists of an instruction processor (interpreter),
97   * a runtime stack, and machinery to support the instruction set
98   * contained within the tuples.
99   * <p>
100  * The interpreter runs completely independent of the frontend/intermediate step.
101  * In fact, an intermediate file produced by Jawk is sufficient to
102  * execute on this interpreter. The binding data-structure is
103  * the {@link AwkSettings}, which can contain options pertinent to
104  * the interpreter. For example, the interpreter must know about
105  * the -v command line argument values, as well as the file/variable list
106  * parameter values (ARGC/ARGV) after the script on the command line.
107  * However, if programmatic access to the AVM is required, meaningful
108  * {@link AwkSettings} are not required.
109  * <p>
110  * Semantic analysis has occurred prior to execution of the interpreter.
111  * Therefore, the interpreter throws AwkRuntimeExceptions upon most
112  * errors/conditions. It can also throw a <code>java.lang.Error</code> if an
113  * interpreter error is encountered.
114  * <p>
115  * AVM instances are reusable, but they are not thread-safe. Reuse the same
116  * interpreter only sequentially, or create one AVM per concurrent execution.
117  * When AVM is used directly, callers own its lifecycle and must
118  * {@link #close()} it when done.
119  *
120  * @author Danny Daglas
121  */
122 public class AVM implements VariableManager, Closeable {
123 
124 	private RuntimeStack runtimeStack = new RuntimeStack();
125 
126 	// operand stack
127 	private Deque<Object> operandStack = new ArrayDeque<Object>();
128 	private List<String> arguments;
129 	private boolean sortedArrayKeys;
130 	private final Map<String, Object> baseInitialVariables;
131 	private final Map<String, Object> baseSpecialVariables;
132 	private Map<String, Object> executionInitialVariables;
133 	private Map<String, Object> executionSpecialVariables;
134 	private JRT jrt;
135 	private Map<String, JawkExtension> extensionInstances;
136 
137 	private static final Object NULL_OPERAND = new Object();
138 
139 	// stack methods
140 	// private Object pop() { return operandStack.removeFirst(); }
141 	// private void push(Object o) { operandStack.addLast(o); }
142 	private Object pop() {
143 		Object value = operandStack.pop();
144 		return value == NULL_OPERAND ? null : value;
145 	}
146 
147 	private void push(Object o) {
148 		operandStack.push(o == null ? NULL_OPERAND : o);
149 	}
150 
151 	private final AwkSettings settings;
152 	private final boolean profiling;
153 	private final Map<Opcode, ProfilingReport.Accumulator> tupleProfilingStats;
154 	private final Map<String, ProfilingReport.Accumulator> functionProfilingStats;
155 	private final Deque<ActiveFunction> activeProfilingFunctions;
156 	private boolean inputSourceFilelistAssignmentsApplied;
157 	private InputSource resolvedInputSource;
158 	private AwkExpression installedEvalExpression;
159 	private boolean mergedGlobalLayoutActive;
160 
161 	/**
162 	 * Construct the interpreter.
163 	 * <p>
164 	 * Provided to allow programmatic construction of the interpreter
165 	 * outside of the framework which is used by Jawk.
166 	 */
167 	public AVM() {
168 		this(null, Collections.<String, JawkExtension>emptyMap());
169 	}
170 
171 	/**
172 	 * Construct the interpreter, accepting parameters which may have been
173 	 * set on the command-line arguments to the JVM.
174 	 *
175 	 * @param parameters The parameters affecting the behavior of the
176 	 *        interpreter.
177 	 * @param extensionInstances Map of the extensions to load
178 	 */
179 	public AVM(final AwkSettings parameters,
180 			final Map<String, JawkExtension> extensionInstances) {
181 		this(parameters, extensionInstances, false);
182 	}
183 
184 	/**
185 	 * Construct the interpreter, optionally enabling runtime profiling.
186 	 *
187 	 * @param parameters The parameters affecting the behavior of the
188 	 *        interpreter.
189 	 * @param extensionInstances Map of the extensions to load
190 	 * @param profilingEnabled Whether to collect profiling statistics
191 	 */
192 	public AVM(
193 			final AwkSettings parameters,
194 			final Map<String, JawkExtension> extensionInstances,
195 			final boolean profilingEnabled) {
196 		this.settings = parameters != null ? parameters : AwkSettings.DEFAULT_SETTINGS;
197 		this.extensionInstances = extensionInstances == null ?
198 				Collections.<String, JawkExtension>emptyMap() : extensionInstances;
199 		this.profiling = profilingEnabled;
200 		if (profilingEnabled) {
201 			this.tupleProfilingStats = new java.util.EnumMap<Opcode, ProfilingReport.Accumulator>(Opcode.class);
202 			this.functionProfilingStats = new LinkedHashMap<String, ProfilingReport.Accumulator>();
203 			this.activeProfilingFunctions = new ArrayDeque<ActiveFunction>();
204 		} else {
205 			this.tupleProfilingStats = null;
206 			this.functionProfilingStats = null;
207 			this.activeProfilingFunctions = null;
208 		}
209 
210 		arguments = Collections.emptyList();
211 		sortedArrayKeys = this.settings.isUseSortedArrayKeys();
212 		baseInitialVariables = new HashMap<String, Object>(this.settings.getVariables());
213 		baseSpecialVariables = JRT.copySpecialVariables(baseInitialVariables);
214 		executionInitialVariables = baseInitialVariables;
215 		executionSpecialVariables = baseSpecialVariables;
216 
217 		jrt = createJrt();
218 		initExtensions();
219 	}
220 
221 	protected JRT createJrt() {
222 		return new JRT(this, this.settings.getLocale(), AwkSink.NOP_SINK, null);
223 	}
224 
225 	/**
226 	 * Returns the runtime settings associated with this interpreter.
227 	 *
228 	 * @return the settings, never {@code null}
229 	 */
230 	protected AwkSettings getSettings() {
231 		return settings;
232 	}
233 
234 	/**
235 	 * Returns the JRT (Jawk Runtime) instance associated with this interpreter.
236 	 *
237 	 * @return the JRT instance, never {@code null}
238 	 */
239 	@SuppressFBWarnings("EI_EXPOSE_REP")
240 	public JRT getJrt() {
241 		return jrt;
242 	}
243 
244 	/**
245 	 * Sets the sink used by default {@code print} and {@code printf}
246 	 * operations on this runtime.
247 	 *
248 	 * @param sink sink to use
249 	 */
250 	public void setAwkSink(AwkSink sink) {
251 		jrt.setAwkSink(Objects.requireNonNull(sink, "sink"));
252 	}
253 
254 	/**
255 	 * Sets the stream used for the stderr output of spawned processes
256 	 * (e.g.&nbsp;{@code system("...")}).
257 	 *
258 	 * @param errorStream stream to receive process stderr
259 	 */
260 	public void setErrorStream(PrintStream errorStream) {
261 		jrt.setErrorStream(errorStream);
262 	}
263 
264 	/**
265 	 * Returns the default sink used by this runtime.
266 	 *
267 	 * @return the current AWK sink
268 	 */
269 	public AwkSink getAwkSink() {
270 		return jrt.getAwkSink();
271 	}
272 
273 	/**
274 	 * Returns the locale configured for this runtime.
275 	 *
276 	 * @return runtime locale
277 	 */
278 	protected Locale getLocale() {
279 		return jrt.getLocale();
280 	}
281 
282 	/**
283 	 * Evaluates a compiled expression against the AVM state exactly as it
284 	 * currently stands.
285 	 *
286 	 * @param expression compiled expression to evaluate
287 	 * @return the resulting value
288 	 * @throws IOException if evaluation fails
289 	 */
290 	public Object eval(AwkExpression expression) throws IOException {
291 		AwkExpression compiledExpression = Objects.requireNonNull(expression, "expression");
292 		installExpressionMetadata(compiledExpression);
293 
294 		try {
295 			executeTuples(compiledExpression.top());
296 		} catch (ExitException e) {
297 			// Expression tuples must never contain EXIT opcodes. If callers pass an
298 			// invalid compiled expression, fail fast without poisoning later evals.
299 			throwExitException = false;
300 			exitCode = 0;
301 			throw new IllegalStateException("eval(AwkExpression) cannot execute EXIT opcodes.", e);
302 		}
303 		return operandStack.isEmpty() ? null : JRT.toJavaScalar(pop());
304 	}
305 
306 	/**
307 	 * Evaluates a compiled expression against the supplied input source.
308 	 *
309 	 * @param expression compiled expression to evaluate
310 	 * @param inputSource input source providing the current record
311 	 * @return the resulting value
312 	 * @throws IOException if evaluation fails
313 	 */
314 	public Object eval(AwkExpression expression, InputSource inputSource) throws IOException {
315 		return eval(expression, inputSource, null);
316 	}
317 
318 	/**
319 	 * Evaluates a compiled expression against the supplied input source with
320 	 * per-call variable overrides.
321 	 *
322 	 * @param expression compiled expression to evaluate
323 	 * @param inputSource input source providing the current record
324 	 * @param variableOverrides additional variable assignments applied on top of
325 	 *        the settings-level variables (may be {@code null})
326 	 * @return the resulting value
327 	 * @throws IOException if evaluation fails
328 	 */
329 	public Object eval(
330 			AwkExpression expression,
331 			InputSource inputSource,
332 			Map<String, Object> variableOverrides)
333 			throws IOException {
334 		prepareForEval(inputSource, Collections.<String>emptyList(), variableOverrides);
335 		return eval(expression);
336 	}
337 
338 	/**
339 	 * Executes a compiled AWK program with the current runtime defaults.
340 	 *
341 	 * @param program compiled program to execute
342 	 * @param inputSource input source providing records
343 	 * @throws ExitException when the program terminates via {@code exit}
344 	 * @throws IOException if execution fails
345 	 */
346 	public void execute(AwkProgram program, InputSource inputSource) throws ExitException, IOException {
347 		execute(program, inputSource, Collections.<String>emptyList(), null);
348 	}
349 
350 	/**
351 	 * Executes a compiled AWK program with explicit runtime arguments.
352 	 *
353 	 * @param program compiled program to execute
354 	 * @param inputSource input source providing records
355 	 * @param runtimeArguments name=value or filename entries from the command line
356 	 * @throws ExitException when the program terminates via {@code exit}
357 	 * @throws IOException if execution fails
358 	 */
359 	public void execute(AwkProgram program, InputSource inputSource, List<String> runtimeArguments)
360 			throws ExitException,
361 			IOException {
362 		execute(program, inputSource, runtimeArguments, null);
363 	}
364 
365 	/**
366 	 * Executes a compiled AWK program with explicit runtime arguments and
367 	 * variable overrides.
368 	 *
369 	 * @param program compiled program to execute
370 	 * @param inputSource input source providing records
371 	 * @param runtimeArguments name=value or filename entries from the command line
372 	 * @param variableOverrides additional variable assignments applied on top of
373 	 *        the settings-level variables (may be {@code null})
374 	 * @throws ExitException when the program terminates via {@code exit}
375 	 * @throws IOException if execution fails
376 	 */
377 	public void execute(
378 			AwkProgram program,
379 			InputSource inputSource,
380 			List<String> runtimeArguments,
381 			Map<String, Object> variableOverrides)
382 			throws ExitException,
383 			IOException {
384 		AwkProgram compiledProgram = Objects.requireNonNull(program, "program");
385 		InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
386 		resetRuntimeState(runtimeArguments, variableOverrides);
387 		installProgramMetadata(compiledProgram);
388 
389 		jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
390 		if (!executionSpecialVariables.isEmpty()) {
391 			jrt.applySpecialVariables(executionSpecialVariables);
392 		}
393 		rebindResolvedInputSource(resolvedSource);
394 		executeTuples(compiledProgram.top());
395 	}
396 
397 	/**
398 	 * Executes a compiled AWK program while persisting user-defined global
399 	 * variables across repeated executions on this AVM instance.
400 	 * <p>
401 	 * Before the new program starts, this method imports any user-defined
402 	 * globals currently materialized in the AVM and remaps them onto the
403 	 * incoming program's compiled global slots.
404 	 *
405 	 * @param program compiled program to execute
406 	 * @param inputSource input source providing records
407 	 * @throws ExitException when the program terminates via {@code exit}
408 	 * @throws IOException if execution fails
409 	 */
410 	public void executePersistingGlobals(AwkProgram program, InputSource inputSource)
411 			throws ExitException,
412 			IOException {
413 		executePersistingGlobals(program, inputSource, Collections.<String>emptyList(), null);
414 	}
415 
416 	/**
417 	 * Executes a compiled AWK program while persisting user-defined global
418 	 * variables across repeated executions on this AVM instance.
419 	 * <p>
420 	 * Before the new program starts, this method imports any user-defined
421 	 * globals currently materialized in the AVM and remaps them onto the
422 	 * incoming program's compiled global slots.
423 	 *
424 	 * @param program compiled program to execute
425 	 * @param inputSource input source providing records
426 	 * @param runtimeArguments name=value or filename entries from the command line
427 	 * @throws ExitException when the program terminates via {@code exit}
428 	 * @throws IOException if execution fails
429 	 */
430 	public void executePersistingGlobals(
431 			AwkProgram program,
432 			InputSource inputSource,
433 			List<String> runtimeArguments)
434 			throws ExitException,
435 			IOException {
436 		executePersistingGlobals(program, inputSource, runtimeArguments, null);
437 	}
438 
439 	/**
440 	 * Executes a compiled AWK program while persisting user-defined global
441 	 * variables across repeated executions on this AVM instance.
442 	 * <p>
443 	 * Before the new program starts, this method imports any user-defined
444 	 * globals currently materialized in the AVM and remaps them onto the
445 	 * incoming program's compiled global slots.
446 	 *
447 	 * @param program compiled program to execute
448 	 * @param inputSource input source providing records
449 	 * @param runtimeArguments name=value or filename entries from the command line
450 	 * @param variableOverrides additional variable assignments applied on top of
451 	 *        the settings-level variables (may be {@code null})
452 	 * @throws ExitException when the program terminates via {@code exit}
453 	 * @throws IOException if execution fails
454 	 */
455 	public void executePersistingGlobals(
456 			AwkProgram program,
457 			InputSource inputSource,
458 			List<String> runtimeArguments,
459 			Map<String, Object> variableOverrides)
460 			throws ExitException,
461 			IOException {
462 		AwkProgram compiledProgram = Objects.requireNonNull(program, "program");
463 		InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
464 		mergeRuntimeState(runtimeArguments, variableOverrides, compiledProgram);
465 
466 		jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
467 		if (!executionSpecialVariables.isEmpty()) {
468 			jrt.applySpecialVariables(executionSpecialVariables);
469 		}
470 		rebindResolvedInputSource(resolvedSource);
471 		executeTuples(compiledProgram.top());
472 	}
473 
474 	/**
475 	 * Clears the user-defined globals retained in the current runtime stack.
476 	 * <p>
477 	 * The next {@link #executePersistingGlobals(AwkProgram, InputSource, List, Map)}
478 	 * call will therefore start from an empty persistent global bank.
479 	 */
480 	public void clearPersistentGlobals() {
481 		runtimeStack.clearGlobals();
482 		mergedGlobalLayoutActive = false;
483 	}
484 
485 	/**
486 	 * Captures the user-defined globals currently retained by this AVM for
487 	 * persistent execution.
488 	 * <p>
489 	 * The returned snapshot is serializable and can later be fed back into
490 	 * {@link #restorePersistentMemory(Map)} on this or another AVM instance.
491 	 *
492 	 * @return serializable snapshot of the persistent user-global bank
493 	 */
494 	public Map<String, Object> snapshotPersistentMemory() {
495 		return new LinkedHashMap<>(collectPersistentGlobalValues());
496 	}
497 
498 	/**
499 	 * Restores the user-defined globals retained by this AVM from a previously
500 	 * captured persistent-memory snapshot.
501 	 * <p>
502 	 * Restoring a snapshot replaces the current retained global bank. The next
503 	 * {@link #executePersistingGlobals(AwkProgram, InputSource, List, Map)} call
504 	 * will merge these globals into the compiled layout of the incoming program.
505 	 *
506 	 * @param snapshot snapshot to restore
507 	 */
508 	public void restorePersistentMemory(Map<String, Object> snapshot) {
509 		Map<String, Object> restoredSnapshot = Objects.requireNonNull(snapshot, "snapshot");
510 		Map<String, Object> restoredGlobals = filterToPersistentEligible(restoredSnapshot);
511 		runtimeStack.clearGlobals();
512 		if (!restoredGlobals.isEmpty()) {
513 			runtimeStack.rebindGlobals(new ArrayList<>(restoredGlobals.keySet()));
514 			applyGlobalsToStack(restoredGlobals);
515 		}
516 		mergedGlobalLayoutActive = false;
517 	}
518 
519 	private void initExtensions() {
520 		if (extensionInstances.isEmpty()) {
521 			return;
522 		}
523 		Set<JawkExtension> initialized = new LinkedHashSet<JawkExtension>();
524 		for (JawkExtension extension : extensionInstances.values()) {
525 			if (initialized.add(extension)) {
526 				extension.init(this, jrt, settings); // this = VariableManager
527 			}
528 		}
529 	}
530 
531 	// Offsets for globals that remain runtime-managed by the tuple stream.
532 	// ARGC is always materialized; ENVIRON and ARGV are emitted on demand.
533 	private long environOffset = NULL_OFFSET;
534 	private long argcOffset = NULL_OFFSET;
535 	private long argvOffset = NULL_OFFSET;
536 
537 	private static final Integer ZERO = Integer.valueOf(0);
538 	private static final Integer ONE = Integer.valueOf(1);
539 
540 	/** Random number generator used for rand() */
541 	private final BSDRandom randomNumberGenerator = new BSDRandom(1);
542 
543 	/**
544 	 * Last seed value used with {@code srand()}.
545 	 * <p>
546 	 * The default seed for {@code rand()} in One True Awk is {@code 1}, so
547 	 * we initialize {@code oldseed} with this value to mimic that
548 	 * behaviour. This ensures deterministic sequences until the user
549 	 * explicitly calls {@code srand()}.
550 	 */
551 	private int oldseed = 1;
552 
553 	private Address exitAddress = null;
554 
555 	/**
556 	 * <code>true</code> if execution position is within an END block;
557 	 * <code>false</code> otherwise.
558 	 */
559 	private boolean withinEndBlocks = false;
560 
561 	/**
562 	 * Exit code set by the <code>exit NN</code> command (0 by default)
563 	 */
564 	private int exitCode = 0;
565 
566 	/**
567 	 * Whether <code>exit</code> has been called and we should throw ExitException
568 	 */
569 	private boolean throwExitException = false;
570 
571 	/**
572 	 * Maps global variable names to their global array offsets.
573 	 * It is useful when passing variable assignments from the file-list
574 	 * portion of the command-line arguments.
575 	 */
576 	private Map<String, Integer> globalVariableOffsets;
577 	/**
578 	 * Indicates whether the variable, by name, is a scalar
579 	 * or not. If not, then it is an Associative Array.
580 	 */
581 	private Map<String, Boolean> globalVariableArrays;
582 	private Set<String> functionNames = Collections.emptySet();
583 	private Map<String, Integer> initializedEvalGlobalVariableOffsets;
584 	private Map<String, Boolean> initializedEvalGlobalVariableArrays;
585 
586 	/**
587 	 * Resets the interpreter to a fresh eval state and binds one text record as
588 	 * the current input.
589 	 *
590 	 * @param input text record to expose as {@code $0}
591 	 * @return {@code true} when a record was prepared, {@code false} when the
592 	 *         provided text represents no input
593 	 * @throws IOException if binding the input fails
594 	 */
595 	public boolean prepareForEval(String input) throws IOException {
596 		return prepareForEval(new SingleRecordInputSource(input), Collections.<String>emptyList(), null);
597 	}
598 
599 	/**
600 	 * Resets the interpreter to a fresh eval state and binds at most one record
601 	 * from the provided input source as the current input. Calling this method
602 	 * again on the same source advances to the next available record.
603 	 *
604 	 * @param inputSource source providing the record to bind
605 	 * @return {@code true} when a record was prepared, {@code false} when the
606 	 *         source is exhausted
607 	 * @throws IOException if reading the input fails
608 	 */
609 	public boolean prepareForEval(InputSource inputSource) throws IOException {
610 		return prepareForEval(inputSource, Collections.<String>emptyList(), null);
611 	}
612 
613 	private boolean prepareForEval(
614 			InputSource inputSource,
615 			List<String> runtimeArguments,
616 			Map<String, Object> variableOverrides)
617 			throws IOException {
618 		InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
619 		resetRuntimeState(runtimeArguments, variableOverrides);
620 		rebindResolvedInputSource(resolvedSource);
621 
622 		jrt.jrtCloseAll();
623 		jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
624 		if (!executionSpecialVariables.isEmpty()) {
625 			jrt.applySpecialVariables(executionSpecialVariables);
626 		}
627 		return jrt.consumeInputForEval(resolvedInputSource);
628 	}
629 
630 	private void resetRuntimeState(List<String> runtimeArguments, Map<String, Object> variableOverrides) {
631 		resetTransientRuntimeState(runtimeArguments, variableOverrides);
632 		runtimeStack.clearGlobals();
633 	}
634 
635 	private void resetTransientRuntimeState(List<String> runtimeArguments, Map<String, Object> variableOverrides) {
636 		// Reset the AVM-owned state that must not leak across executions.
637 		operandStack.clear();
638 		environOffset = NULL_OFFSET;
639 		argcOffset = NULL_OFFSET;
640 		argvOffset = NULL_OFFSET;
641 		exitAddress = null;
642 		withinEndBlocks = false;
643 		exitCode = 0;
644 		throwExitException = false;
645 		inputSourceFilelistAssignmentsApplied = false;
646 		globalVariableOffsets = null;
647 		globalVariableArrays = null;
648 		functionNames = Collections.emptySet();
649 		initializedEvalGlobalVariableOffsets = null;
650 		initializedEvalGlobalVariableArrays = null;
651 		installedEvalExpression = null;
652 		mergedGlobalLayoutActive = false;
653 		runtimeStack.resetTransientState();
654 		randomNumberGenerator.setSeed(1);
655 		oldseed = 1;
656 
657 		prepareExecutionInputs(runtimeArguments, variableOverrides);
658 	}
659 
660 	private void installExpressionMetadata(AwkExpression compiledExpression) {
661 		if (installedEvalExpression == compiledExpression) {
662 			return;
663 		}
664 		globalVariableOffsets = compiledExpression.getGlobalVariableOffsetMap();
665 		globalVariableArrays = compiledExpression.getGlobalVariableAarrayMap();
666 		functionNames = compiledExpression.getFunctionNameSet();
667 		installedEvalExpression = compiledExpression;
668 	}
669 
670 	private void installProgramMetadata(AwkProgram compiledProgram) {
671 		globalVariableOffsets = compiledProgram.getGlobalVariableOffsetMap();
672 		globalVariableArrays = compiledProgram.getGlobalVariableAarrayMap();
673 		functionNames = compiledProgram.getFunctionNameSet();
674 	}
675 
676 	private void rebindResolvedInputSource(InputSource resolvedSource) {
677 		InputSource previousResolvedSource = resolvedInputSource;
678 		if (previousResolvedSource != null && previousResolvedSource != resolvedSource) {
679 			closeInputSource(previousResolvedSource);
680 		}
681 		resolvedInputSource = resolvedSource;
682 	}
683 
684 	private boolean hasCompatibleEvalGlobalLayout(long numGlobals) {
685 		Object[] globals = runtimeStack.getNumGlobals();
686 		return globals != null
687 				&& globals.length == numGlobals
688 				&& Objects.equals(initializedEvalGlobalVariableOffsets, globalVariableOffsets)
689 				&& Objects.equals(initializedEvalGlobalVariableArrays, globalVariableArrays);
690 	}
691 
692 	/**
693 	 * Resets transient execution state, installs the new program metadata, and
694 	 * merges the previously retained user globals into the new compiled global
695 	 * layout.
696 	 *
697 	 * @param runtimeArguments name=value or filename entries for this execution
698 	 * @param variableOverrides per-call variable overrides for this execution
699 	 * @param compiledProgram program whose global layout should become active
700 	 */
701 	private void mergeRuntimeState(
702 			List<String> runtimeArguments,
703 			Map<String, Object> variableOverrides,
704 			AwkProgram compiledProgram) {
705 		Map<String, Object> carriedGlobals = collectPersistentGlobalValues();
706 		resetTransientRuntimeState(runtimeArguments, variableOverrides);
707 		installProgramMetadata(compiledProgram);
708 
709 		Map<String, Object> basePersistentSeeds = collectBasePersistentGlobalSeeds();
710 		Map<String, Object> executionUserSeeds = collectExecutionUserGlobalSeeds(variableOverrides);
711 		List<String> mergedGlobalNamesByOffset = buildMergedGlobalNamesByOffset(
712 				carriedGlobals,
713 				basePersistentSeeds,
714 				executionUserSeeds);
715 
716 		runtimeStack.rebindGlobals(mergedGlobalNamesByOffset);
717 		applyGlobalsToStack(carriedGlobals);
718 		applyGlobalsToStack(basePersistentSeeds);
719 		applyGlobalsToStack(executionUserSeeds);
720 		mergedGlobalLayoutActive = true;
721 	}
722 
723 	/**
724 	 * Returns whether the current runtime stack already contains a merged global
725 	 * layout for the compiled program about to execute.
726 	 * <p>
727 	 * Persistent execution may append previously retained globals after the
728 	 * compiled globals of the incoming program. The tuple stream only dereferences
729 	 * the prefix defined by {@code SET_NUM_GLOBALS}, so appended globals are valid
730 	 * as long as the compiled prefix still matches name-for-name and offset-for-offset.
731 	 *
732 	 * @param numGlobals number of globals compiled into the active program
733 	 * @return {@code true} when the merged layout is compatible with the active
734 	 *         program
735 	 */
736 	private boolean hasCompatiblePersistentGlobalLayout(long numGlobals) {
737 		Object[] globals = runtimeStack.getNumGlobals();
738 		if (!mergedGlobalLayoutActive
739 				|| globals == null
740 				|| globalVariableOffsets == null
741 				|| globals.length < numGlobals) {
742 			return false;
743 		}
744 		for (Map.Entry<String, Integer> entry : globalVariableOffsets.entrySet()) {
745 			int offset = entry.getValue().intValue();
746 			if (offset < 0 || offset >= globals.length || !entry.getKey().equals(runtimeStack.getGlobalName(offset))) {
747 				return false;
748 			}
749 		}
750 		return true;
751 	}
752 
753 	/**
754 	 * Applies execution-level initial variables to stack-managed globals.
755 	 * <p>
756 	 * Plain {@link #execute(AwkProgram, InputSource, List, Map)} calls apply all
757 	 * compatible globals here. Persistent executions only reapply non-persistent
758 	 * globals so carried user globals are not overwritten unless they were
759 	 * explicitly supplied for the current run.
760 	 *
761 	 * @param skipPersistentEligibleGlobals whether persistent user globals should
762 	 *        be skipped by this application pass
763 	 */
764 	private void applyExecutionInitialVariablesToGlobalSlots(boolean skipPersistentEligibleGlobals) {
765 		for (Map.Entry<String, Object> entry : executionInitialVariables.entrySet()) {
766 			String key = entry.getKey();
767 			if (skipPersistentEligibleGlobals && isPersistentEligibleGlobal(key)) {
768 				continue;
769 			}
770 			if (functionNames.contains(key)) {
771 				throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + key + ").");
772 			}
773 			Integer offsetObj = globalVariableOffsets.get(key);
774 			Boolean arrayObj = globalVariableArrays.get(key);
775 			if (offsetObj != null) {
776 				Object obj = normalizeExternalVariableValue(entry.getValue());
777 				if (arrayObj.booleanValue()) {
778 					if (obj instanceof Map) {
779 						runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
780 					} else {
781 						throw new IllegalArgumentException(
782 								"Cannot assign a scalar to a non-scalar variable (" + key + ").");
783 					}
784 				} else {
785 					runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
786 				}
787 			}
788 		}
789 	}
790 
791 	/**
792 	 * Prepares the per-execution runtime arguments and variable overrides.
793 	 * <p>
794 	 * Base settings-level variables remain the default source. Per-call overrides
795 	 * are layered on top without mutating the base snapshot held by this AVM.
796 	 *
797 	 * @param runtimeArguments name=value or filename entries for this execution
798 	 * @param variableOverrides per-call variable overrides for this execution
799 	 */
800 	private void prepareExecutionInputs(
801 			List<String> runtimeArguments,
802 			Map<String, Object> variableOverrides) {
803 		this.arguments = runtimeArguments != null ? new ArrayList<>(runtimeArguments) : Collections.<String>emptyList();
804 
805 		if (variableOverrides == null || variableOverrides.isEmpty()) {
806 			executionInitialVariables = baseInitialVariables;
807 			executionSpecialVariables = baseSpecialVariables;
808 		} else {
809 			executionInitialVariables = new HashMap<>(baseInitialVariables);
810 			executionInitialVariables.putAll(variableOverrides);
811 
812 			Map<String, Object> specialOverrides = JRT.copySpecialVariables(variableOverrides);
813 			if (specialOverrides.isEmpty()) {
814 				executionSpecialVariables = baseSpecialVariables;
815 			} else {
816 				executionSpecialVariables = new HashMap<>(baseSpecialVariables);
817 				executionSpecialVariables.putAll(specialOverrides);
818 			}
819 		}
820 	}
821 
822 	/**
823 	 * Filters the given map to retain only entries whose keys are
824 	 * persistent-eligible globals.
825 	 *
826 	 * @param source source map to filter
827 	 * @return insertion-ordered map containing only persistent-eligible entries
828 	 */
829 	private Map<String, Object> filterToPersistentEligible(Map<String, Object> source) {
830 		Map<String, Object> result = new LinkedHashMap<>();
831 		for (Map.Entry<String, Object> entry : source.entrySet()) {
832 			if (isPersistentEligibleGlobal(entry.getKey())) {
833 				result.put(entry.getKey(), entry.getValue());
834 			}
835 		}
836 		return result;
837 	}
838 
839 	/**
840 	 * Collects the current user-defined globals retained in the runtime stack.
841 	 *
842 	 * @return retained user globals keyed by name, in current runtime order
843 	 */
844 	private Map<String, Object> collectPersistentGlobalValues() {
845 		return filterToPersistentEligible(runtimeStack.snapshotGlobalVariables());
846 	}
847 
848 	/**
849 	 * Collects the AVM-wide baseline variables that should be reapplied before
850 	 * each persistent execution.
851 	 *
852 	 * @return baseline user globals keyed by name
853 	 */
854 	private Map<String, Object> collectBasePersistentGlobalSeeds() {
855 		Map<String, Object> basePersistentSeeds = new LinkedHashMap<>();
856 		for (Map.Entry<String, Object> entry : baseInitialVariables.entrySet()) {
857 			String name = entry.getKey();
858 			if (isPersistentEligibleGlobal(name)) {
859 				validateSeededGlobalName(name);
860 				Object value = normalizeExternalVariableValue(entry.getValue());
861 				validateSeededGlobalValue(name, value);
862 				basePersistentSeeds.put(name, value);
863 			}
864 		}
865 		return basePersistentSeeds;
866 	}
867 
868 	/**
869 	 * Collects the user-defined variables that should override the retained
870 	 * global bank for the current persistent execution.
871 	 * <p>
872 	 * Only user globals are included here. JRT-managed special variables still
873 	 * flow through the normal execution setup.
874 	 *
875 	 * @param variableOverrides per-call variable overrides for this execution
876 	 * @return insertion-ordered overriding seed values keyed by variable name
877 	 */
878 	private Map<String, Object> collectExecutionUserGlobalSeeds(Map<String, Object> variableOverrides) {
879 		Map<String, Object> executionUserSeeds = new LinkedHashMap<>();
880 		if (variableOverrides != null) {
881 			for (Map.Entry<String, Object> entry : variableOverrides.entrySet()) {
882 				String name = entry.getKey();
883 				if (isPersistentEligibleGlobal(name)) {
884 					validateSeededGlobalName(name);
885 					Object value = normalizeExternalVariableValue(entry.getValue());
886 					validateSeededGlobalValue(name, value);
887 					executionUserSeeds.put(name, value);
888 				}
889 			}
890 		}
891 		return executionUserSeeds;
892 	}
893 
894 	/**
895 	 * Builds the slot order for the next persistent execution.
896 	 * <p>
897 	 * The compiled globals are always installed first in their compiled offset
898 	 * order. Retained globals and seeded user globals that are not compiled by
899 	 * the incoming program are appended afterwards so future runs can still reuse
900 	 * them without changing the current program's compiled offsets.
901 	 *
902 	 * @param carriedGlobals retained user globals from the previous execution
903 	 * @param basePersistentSeeds baseline user globals coming from the AVM settings
904 	 * @param executionUserSeeds per-call user overrides for this execution
905 	 * @return merged slot-to-name layout for the next persistent run
906 	 */
907 	private List<String> buildMergedGlobalNamesByOffset(
908 			Map<String, Object> carriedGlobals,
909 			Map<String, Object> basePersistentSeeds,
910 			Map<String, Object> executionUserSeeds) {
911 		LinkedHashSet<String> orderedNames = new LinkedHashSet<>();
912 		List<Map.Entry<String, Integer>> compiledGlobals = new ArrayList<>(globalVariableOffsets.entrySet());
913 		compiledGlobals.sort(java.util.Comparator.comparingInt(Map.Entry::getValue));
914 		for (Map.Entry<String, Integer> entry : compiledGlobals) {
915 			orderedNames.add(entry.getKey());
916 		}
917 		orderedNames.addAll(carriedGlobals.keySet());
918 		orderedNames.addAll(basePersistentSeeds.keySet());
919 		orderedNames.addAll(executionUserSeeds.keySet());
920 		return new ArrayList<>(orderedNames);
921 	}
922 
923 	/**
924 	 * Writes each entry from {@code globals} into the corresponding named slot in
925 	 * the runtime stack.
926 	 *
927 	 * @param globals map of variable name to value to apply
928 	 */
929 	private void applyGlobalsToStack(Map<String, Object> globals) {
930 		for (Map.Entry<String, Object> entry : globals.entrySet()) {
931 			runtimeStack.setGlobalVariable(entry.getKey(), entry.getValue());
932 		}
933 	}
934 
935 	/**
936 	 * Returns whether the given global name should participate in persistent
937 	 * memory.
938 	 *
939 	 * @param name global variable name
940 	 * @return {@code true} when the variable should persist across runs
941 	 */
942 	private boolean isPersistentEligibleGlobal(String name) {
943 		return name != null
944 				&& !JRT.isJrtManagedSpecialVariable(name)
945 				&& !NON_PERSISTENT_GLOBALS.contains(name);
946 	}
947 
948 	/**
949 	 * Validates that a seeded global name is compatible with the compiled
950 	 * metadata of the current program before any value normalization can mutate a
951 	 * caller-provided object graph.
952 	 *
953 	 * @param name variable name to validate
954 	 */
955 	private void validateSeededGlobalName(String name) {
956 		if (functionNames.contains(name)) {
957 			throw new IllegalArgumentException("Cannot assign a value to a function name (" + name + ").");
958 		}
959 	}
960 
961 	/**
962 	 * Validates that a normalized seeded global value is compatible with the
963 	 * compiled metadata of the current program.
964 	 *
965 	 * @param name variable name to validate
966 	 * @param value proposed seeded value after normalization
967 	 */
968 	private void validateSeededGlobalValue(String name, Object value) {
969 		Boolean arrayObj = globalVariableArrays.get(name);
970 		if (Boolean.TRUE.equals(arrayObj) && !(value instanceof Map)) {
971 			throw new IllegalArgumentException("Cannot assign a scalar to a non-scalar variable (" + name + ").");
972 		}
973 	}
974 
975 	/**
976 	 * Parses a runtime {@code name=value} assignment.
977 	 *
978 	 * @param nameValue raw assignment text
979 	 * @return parsed assignment
980 	 */
981 	private NameValueAssignment parseNameValueAssignment(String nameValue) {
982 		int eqIdx = nameValue.indexOf('=');
983 		if (eqIdx == 0) {
984 			throw new IllegalArgumentException(
985 					"Must have a non-blank variable name in a name=value variable assignment argument.");
986 		}
987 		String name = nameValue.substring(0, eqIdx);
988 		String value = nameValue.substring(eqIdx + 1);
989 		return new NameValueAssignment(name, jrt.toInputScalar(value));
990 	}
991 
992 	/**
993 	 * Executes the tuple stream after the runtime has been fully prepared.
994 	 *
995 	 * @param position current position in the tuple stream
996 	 * @throws ExitException when the AWK program executes {@code exit}
997 	 * @throws IOException when runtime input operations fail
998 	 */
999 	private void executeTuples(PositionTracker position)
1000 			throws ExitException,
1001 			IOException {
1002 		Map<Integer, ConditionPair> conditionPairs = null;
1003 		Opcode opcode = null;
1004 		long tupleStartNanos = 0L;
1005 		try {
1006 			while (!position.isEOF()) {
1007 				// System_out.println("--> "+position);
1008 				Tuple tuple = position.current();
1009 				opcode = tuple.getOpcode();
1010 				if (profiling) {
1011 					tupleStartNanos = beforeProfiledTuple(tuple, opcode);
1012 				}
1013 				// switch on OPCODE
1014 				switch (opcode) {
1015 				case PRINT: {
1016 					execPrint((CountTuple) tuple);
1017 					position.next();
1018 					break;
1019 				}
1020 				case PRINT_TO_FILE: {
1021 					execPrintToFile((CountAndAppendTuple) tuple);
1022 					position.next();
1023 					break;
1024 				}
1025 				case PRINT_TO_PIPE: {
1026 					execPrintToPipe((CountTuple) tuple);
1027 					position.next();
1028 					break;
1029 				}
1030 				case PRINTF: {
1031 					execPrintf((CountTuple) tuple);
1032 					position.next();
1033 					break;
1034 				}
1035 				case PRINTF_TO_FILE: {
1036 					execPrintfToFile((CountAndAppendTuple) tuple);
1037 					position.next();
1038 					break;
1039 				}
1040 				case PRINTF_TO_PIPE: {
1041 					execPrintfToPipe((CountTuple) tuple);
1042 					position.next();
1043 					break;
1044 				}
1045 				case SPRINTF: {
1046 					// arg[0] = # of sprintf arguments
1047 					// stack[0] = arg1 (format string)
1048 					// stack[1] = arg2
1049 					// etc.
1050 					CountTuple countTuple = (CountTuple) tuple;
1051 					long numArgs = countTuple.getCount();
1052 					push(sprintfFunction(numArgs));
1053 					position.next();
1054 					break;
1055 				}
1056 				case LENGTH: {
1057 					execLength((CountTuple) tuple);
1058 					position.next();
1059 					break;
1060 				}
1061 				case PUSH_LONG: {
1062 					// arg[0] = long constant to push onto the stack
1063 					PushLongTuple pushTuple = (PushLongTuple) tuple;
1064 					push(pushTuple.getValue());
1065 					position.next();
1066 					break;
1067 				}
1068 				case PUSH_DOUBLE: {
1069 					// arg[0] = double constant to push onto the stack
1070 					PushDoubleTuple pushTuple = (PushDoubleTuple) tuple;
1071 					push(pushTuple.getValue());
1072 					position.next();
1073 					break;
1074 				}
1075 				case PUSH_STRING: {
1076 					// arg[0] = string constant to push onto the stack
1077 					PushStringTuple pushTuple = (PushStringTuple) tuple;
1078 					push(pushTuple.getValue());
1079 					position.next();
1080 					break;
1081 				}
1082 				case POP: {
1083 					// stack[0] = item to pop from the stack
1084 					pop();
1085 					position.next();
1086 					break;
1087 				}
1088 				case IFFALSE: {
1089 					// arg[0] = address to jump to if top of stack is false
1090 					// stack[0] = item to check
1091 
1092 					// if int, then check for 0
1093 					// if double, then check for 0
1094 					// if String, then check for "" or double value of "0"
1095 					boolean jump = !jrt.toBoolean(pop());
1096 					if (jump) {
1097 						position.jump(tuple.getAddress());
1098 					} else {
1099 						position.next();
1100 					}
1101 					break;
1102 				}
1103 				case TO_NUMBER: {
1104 					// stack[0] = item to convert to a number
1105 
1106 					// if int, then check for 0
1107 					// if double, then check for 0
1108 					// if String, then check for "" or double value of "0"
1109 					boolean val = jrt.toBoolean(pop());
1110 					push(val ? ONE : ZERO);
1111 					position.next();
1112 					break;
1113 				}
1114 				case IFTRUE: {
1115 					// arg[0] = address to jump to if top of stack is true
1116 					// stack[0] = item to check
1117 
1118 					// if int, then check for 0
1119 					// if double, then check for 0
1120 					// if String, then check for "" or double value of "0"
1121 					boolean jump = jrt.toBoolean(pop());
1122 					if (jump) {
1123 						position.jump(tuple.getAddress());
1124 					} else {
1125 						position.next();
1126 					}
1127 					break;
1128 				}
1129 				case NOT: {
1130 					// stack[0] = item to logically negate
1131 
1132 					Object o = pop();
1133 
1134 					boolean result = jrt.toBoolean(o);
1135 
1136 					if (result) {
1137 						push(0);
1138 					} else {
1139 						push(1);
1140 					}
1141 					position.next();
1142 					break;
1143 				}
1144 				case NEGATE: {
1145 					// stack[0] = item to numerically negate
1146 
1147 					double d = JRT.toDouble(pop());
1148 					push(-d);
1149 					position.next();
1150 					break;
1151 				}
1152 				case UNARY_PLUS: {
1153 					// stack[0] = item to convert to a number
1154 					double d = JRT.toDouble(pop());
1155 					push(d);
1156 					position.next();
1157 					break;
1158 				}
1159 				case GOTO: {
1160 					// arg[0] = address
1161 
1162 					position.jump(tuple.getAddress());
1163 					break;
1164 				}
1165 				case NOP: {
1166 					// do nothing, just advance the position
1167 					position.next();
1168 					break;
1169 				}
1170 				case CONCAT: {
1171 					// stack[0] = string1
1172 					// stack[1] = string2
1173 					String s2 = jrt.toAwkString(pop());
1174 					String s1 = jrt.toAwkString(pop());
1175 					String resultString = s1 + s2;
1176 					push(resultString);
1177 					position.next();
1178 					break;
1179 				}
1180 				case MULTI_CONCAT: {
1181 					// arg[0] = number of stack items to concatenate
1182 					// stack[0] = last concatenation operand
1183 					CountTuple countTuple = (CountTuple) tuple;
1184 					int count = (int) countTuple.getCount();
1185 					// Store String references so appends run left-to-right. Converting
1186 					// operands to char[] would copy them once before StringBuilder
1187 					// copies them again, and front-inserting would shift existing
1188 					// content on each operand.
1189 					String[] values = new String[count];
1190 					int resultLength = 0;
1191 					for (int i = count - 1; i >= 0; i--) {
1192 						values[i] = jrt.toAwkString(pop());
1193 						resultLength += values[i].length();
1194 					}
1195 					StringBuilder resultString = new StringBuilder(resultLength);
1196 					for (String value : values) {
1197 						resultString.append(value);
1198 					}
1199 					push(resultString.toString());
1200 					position.next();
1201 					break;
1202 				}
1203 				case ASSIGN:
1204 				case ASSIGN_NOPUSH: {
1205 					// arg[0] = offset
1206 					// arg[1] = isGlobal
1207 					// stack[0] = value
1208 					VariableTuple variableTuple = (VariableTuple) tuple;
1209 					Object value = pop();
1210 					assign(
1211 							variableTuple.getVariableOffset(),
1212 							value,
1213 							variableTuple.isGlobal(),
1214 							position,
1215 							opcode == Opcode.ASSIGN);
1216 					position.next();
1217 					break;
1218 				}
1219 				case ASSIGN_ARRAY: {
1220 					// arg[0] = offset
1221 					// arg[1] = isGlobal
1222 					// stack[0] = array index
1223 					// stack[1] = value
1224 					Object arrIdx = pop();
1225 					Object rhs = pop();
1226 					if (rhs == null) {
1227 						rhs = BLANK;
1228 					}
1229 					VariableTuple variableTuple = (VariableTuple) tuple;
1230 					long offset = variableTuple.getVariableOffset();
1231 					boolean isGlobal = variableTuple.isGlobal();
1232 					assignArray(offset, arrIdx, rhs, isGlobal);
1233 					position.next();
1234 					break;
1235 				}
1236 				case ASSIGN_MAP_ELEMENT: {
1237 					// stack[0] = array index
1238 					// stack[1] = associative array
1239 					// stack[2] = value
1240 					Object arrIdx = pop();
1241 					Map<Object, Object> array = toMap(pop());
1242 					Object rhs = pop();
1243 					if (rhs == null) {
1244 						rhs = BLANK;
1245 					}
1246 					assignMapElement(array, arrIdx, rhs);
1247 					position.next();
1248 					break;
1249 				}
1250 				case PLUS_EQ_ARRAY:
1251 				case MINUS_EQ_ARRAY:
1252 				case MULT_EQ_ARRAY:
1253 				case DIV_EQ_ARRAY:
1254 				case MOD_EQ_ARRAY:
1255 				case POW_EQ_ARRAY: {
1256 					// arg[0] = offset
1257 					// arg[1] = isGlobal
1258 					// stack[0] = array index
1259 					// stack[1] = value
1260 					Object arrIdx = pop();
1261 					Object rhs = pop();
1262 					if (rhs == null) {
1263 						rhs = BLANK;
1264 					}
1265 					VariableTuple variableTuple = (VariableTuple) tuple;
1266 					long offset = variableTuple.getVariableOffset();
1267 					boolean isGlobal = variableTuple.isGlobal();
1268 
1269 					double val = JRT.toDouble(rhs);
1270 
1271 					Map<Object, Object> array = ensureMapVariable(offset, isGlobal);
1272 					checkScalar(arrIdx);
1273 					Object o = array.get(arrIdx);
1274 					double origVal = JRT.toDouble(o);
1275 
1276 					double newVal;
1277 
1278 					switch (opcode) {
1279 					case PLUS_EQ_ARRAY:
1280 						newVal = origVal + val;
1281 						break;
1282 					case MINUS_EQ_ARRAY:
1283 						newVal = origVal - val;
1284 						break;
1285 					case MULT_EQ_ARRAY:
1286 						newVal = origVal * val;
1287 						break;
1288 					case DIV_EQ_ARRAY:
1289 						newVal = origVal / val;
1290 						break;
1291 					case MOD_EQ_ARRAY:
1292 						newVal = origVal % val;
1293 						break;
1294 					case POW_EQ_ARRAY:
1295 						newVal = Math.pow(origVal, val);
1296 						break;
1297 					default:
1298 						throw new Error("Invalid op code here: " + opcode);
1299 					}
1300 
1301 					assignArray(offset, arrIdx, newVal, isGlobal);
1302 					position.next();
1303 					break;
1304 				}
1305 				case PLUS_EQ_MAP_ELEMENT:
1306 				case MINUS_EQ_MAP_ELEMENT:
1307 				case MULT_EQ_MAP_ELEMENT:
1308 				case DIV_EQ_MAP_ELEMENT:
1309 				case MOD_EQ_MAP_ELEMENT:
1310 				case POW_EQ_MAP_ELEMENT: {
1311 					// stack[0] = array index
1312 					// stack[1] = associative array
1313 					// stack[2] = value
1314 					Object arrIdx = pop();
1315 					Map<Object, Object> array = toMap(pop());
1316 					Object rhs = pop();
1317 					if (rhs == null) {
1318 						rhs = BLANK;
1319 					}
1320 
1321 					double val = JRT.toDouble(rhs);
1322 					checkScalar(arrIdx);
1323 					Object o = array.get(arrIdx);
1324 					double origVal = JRT.toDouble(o);
1325 					double newVal;
1326 
1327 					switch (opcode) {
1328 					case PLUS_EQ_MAP_ELEMENT:
1329 						newVal = origVal + val;
1330 						break;
1331 					case MINUS_EQ_MAP_ELEMENT:
1332 						newVal = origVal - val;
1333 						break;
1334 					case MULT_EQ_MAP_ELEMENT:
1335 						newVal = origVal * val;
1336 						break;
1337 					case DIV_EQ_MAP_ELEMENT:
1338 						newVal = origVal / val;
1339 						break;
1340 					case MOD_EQ_MAP_ELEMENT:
1341 						newVal = origVal % val;
1342 						break;
1343 					case POW_EQ_MAP_ELEMENT:
1344 						newVal = Math.pow(origVal, val);
1345 						break;
1346 					default:
1347 						throw new Error("Invalid op code here: " + opcode);
1348 					}
1349 
1350 					assignMapElement(array, arrIdx, newVal);
1351 					position.next();
1352 					break;
1353 				}
1354 
1355 				case ASSIGN_AS_INPUT: {
1356 					// stack[0] = value
1357 					jrt.setInputLine(pop());
1358 					push(jrt.getInputLine());
1359 					position.next();
1360 					break;
1361 				}
1362 
1363 				case ASSIGN_AS_INPUT_FIELD: {
1364 					// stack[0] = field number
1365 					// stack[1] = value
1366 					Object fieldNumObj = pop();
1367 					long fieldNum = JRT.parseFieldNumber(fieldNumObj);
1368 					Object value = pop();
1369 					push(value); // leave the result on the stack
1370 					if (fieldNum == 0) {
1371 						jrt.setInputLine(value);
1372 						jrt.jrtParseFields();
1373 					} else {
1374 						jrt.jrtSetInputField(value, fieldNum);
1375 					}
1376 					position.next();
1377 					break;
1378 				}
1379 				case PLUS_EQ:
1380 				case MINUS_EQ:
1381 				case MULT_EQ:
1382 				case DIV_EQ:
1383 				case MOD_EQ:
1384 				case POW_EQ: {
1385 					// arg[0] = offset
1386 					// arg[1] = isGlobal
1387 					// stack[0] = value
1388 					VariableTuple variableTuple = (VariableTuple) tuple;
1389 					long offset = variableTuple.getVariableOffset();
1390 					boolean isGlobal = variableTuple.isGlobal();
1391 					Object o1 = runtimeStack.getVariable(offset, isGlobal);
1392 					if (o1 == null) {
1393 						o1 = BLANK;
1394 					}
1395 					Object o2 = pop();
1396 					double d1 = JRT.toDouble(o1);
1397 					double d2 = JRT.toDouble(o2);
1398 					double ans;
1399 					switch (opcode) {
1400 					case PLUS_EQ:
1401 						ans = d1 + d2;
1402 						break;
1403 					case MINUS_EQ:
1404 						ans = d1 - d2;
1405 						break;
1406 					case MULT_EQ:
1407 						ans = d1 * d2;
1408 						break;
1409 					case DIV_EQ:
1410 						ans = d1 / d2;
1411 						break;
1412 					case MOD_EQ:
1413 						ans = d1 % d2;
1414 						break;
1415 					case POW_EQ:
1416 						ans = Math.pow(d1, d2);
1417 						break;
1418 					default:
1419 						throw new Error("Invalid opcode here: " + opcode);
1420 					}
1421 					push(ans);
1422 					runtimeStack.setVariable(offset, ans, isGlobal);
1423 					position.next();
1424 					break;
1425 				}
1426 				case PLUS_EQ_INPUT_FIELD:
1427 				case MINUS_EQ_INPUT_FIELD:
1428 				case MULT_EQ_INPUT_FIELD:
1429 				case DIV_EQ_INPUT_FIELD:
1430 				case MOD_EQ_INPUT_FIELD:
1431 				case POW_EQ_INPUT_FIELD: {
1432 					// stack[0] = dollar_fieldNumber
1433 					// stack[1] = inc value
1434 
1435 					// same code as GET_INPUT_FIELD:
1436 					long fieldnum = JRT.parseFieldNumber(pop());
1437 					double incval = JRT.toDouble(pop());
1438 
1439 					// except here, get the number, and add the incvalue
1440 					Object numObj = jrt.jrtGetInputField(fieldnum);
1441 					double num;
1442 					switch (opcode) {
1443 					case PLUS_EQ_INPUT_FIELD:
1444 						num = JRT.toDouble(numObj) + incval;
1445 						break;
1446 					case MINUS_EQ_INPUT_FIELD:
1447 						num = JRT.toDouble(numObj) - incval;
1448 						break;
1449 					case MULT_EQ_INPUT_FIELD:
1450 						num = JRT.toDouble(numObj) * incval;
1451 						break;
1452 					case DIV_EQ_INPUT_FIELD:
1453 						num = JRT.toDouble(numObj) / incval;
1454 						break;
1455 					case MOD_EQ_INPUT_FIELD:
1456 						num = JRT.toDouble(numObj) % incval;
1457 						break;
1458 					case POW_EQ_INPUT_FIELD:
1459 						num = Math.pow(JRT.toDouble(numObj), incval);
1460 						break;
1461 					default:
1462 						throw new Error("Invalid opcode here: " + opcode);
1463 					}
1464 					setNumOnJRT(fieldnum, num);
1465 
1466 					// put the result value on the stack
1467 					push(num);
1468 					position.next();
1469 
1470 					break;
1471 				}
1472 				case INC: {
1473 					// arg[0] = offset
1474 					// arg[1] = isGlobal
1475 					VariableTuple variableTuple = (VariableTuple) tuple;
1476 					inc(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1477 					position.next();
1478 					break;
1479 				}
1480 				case DEC: {
1481 					// arg[0] = offset
1482 					// arg[1] = isGlobal
1483 					VariableTuple variableTuple = (VariableTuple) tuple;
1484 					dec(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1485 					position.next();
1486 					break;
1487 				}
1488 				case POSTINC: {
1489 					// arg[0] = offset
1490 					// arg[1] = isGlobal
1491 					pop();
1492 					VariableTuple variableTuple = (VariableTuple) tuple;
1493 					push(inc(variableTuple.getVariableOffset(), variableTuple.isGlobal()));
1494 					position.next();
1495 					break;
1496 				}
1497 				case POSTDEC: {
1498 					// arg[0] = offset
1499 					// arg[1] = isGlobal
1500 					pop();
1501 					VariableTuple variableTuple = (VariableTuple) tuple;
1502 					push(dec(variableTuple.getVariableOffset(), variableTuple.isGlobal()));
1503 					position.next();
1504 					break;
1505 				}
1506 				case INC_ARRAY_REF: {
1507 					// arg[0] = offset
1508 					// arg[1] = isGlobal
1509 					// stack[0] = array index
1510 					VariableTuple variableTuple = (VariableTuple) tuple;
1511 					boolean isGlobal = variableTuple.isGlobal();
1512 					Map<Object, Object> aa = ensureMapVariable(variableTuple.getVariableOffset(), isGlobal);
1513 					Object key = pop();
1514 					checkScalar(key);
1515 					Object o = aa.get(key);
1516 					double ans = JRT.toDouble(o) + 1;
1517 					aa.put(key, ans);
1518 					position.next();
1519 					break;
1520 				}
1521 				case DEC_ARRAY_REF: {
1522 					// arg[0] = offset
1523 					// arg[1] = isGlobal
1524 					// stack[0] = array index
1525 					VariableTuple variableTuple = (VariableTuple) tuple;
1526 					boolean isGlobal = variableTuple.isGlobal();
1527 					Map<Object, Object> aa = ensureMapVariable(variableTuple.getVariableOffset(), isGlobal);
1528 					Object key = pop();
1529 					checkScalar(key);
1530 					Object o = aa.get(key);
1531 					double ans = JRT.toDouble(o) - 1;
1532 					aa.put(key, ans);
1533 					position.next();
1534 					break;
1535 				}
1536 				case INC_MAP_REF: {
1537 					// stack[0] = array index
1538 					// stack[1] = associative array
1539 					Object key = pop();
1540 					checkScalar(key);
1541 					Map<Object, Object> aa = toMap(pop());
1542 					Object o = aa.get(key);
1543 					double ans = JRT.toDouble(o) + 1;
1544 					aa.put(key, ans);
1545 					position.next();
1546 					break;
1547 				}
1548 				case DEC_MAP_REF: {
1549 					// stack[0] = array index
1550 					// stack[1] = associative array
1551 					Object key = pop();
1552 					checkScalar(key);
1553 					Map<Object, Object> aa = toMap(pop());
1554 					Object o = aa.get(key);
1555 					double ans = JRT.toDouble(o) - 1;
1556 					aa.put(key, ans);
1557 					position.next();
1558 					break;
1559 				}
1560 				case INC_DOLLAR_REF: {
1561 					// stack[0] = dollar index (field number)
1562 					long fieldnum = JRT.parseFieldNumber(pop());
1563 
1564 					Object numObj = jrt.jrtGetInputField(fieldnum);
1565 					double original = JRT.toDouble(numObj);
1566 					double num = original + 1;
1567 					setNumOnJRT(fieldnum, num);
1568 
1569 					push(Double.valueOf(original));
1570 
1571 					position.next();
1572 					break;
1573 				}
1574 				case DEC_DOLLAR_REF: {
1575 					// stack[0] = dollar index (field number)
1576 					// same code as GET_INPUT_FIELD:
1577 					long fieldnum = JRT.parseFieldNumber(pop());
1578 
1579 					Object numObj = jrt.jrtGetInputField(fieldnum);
1580 					double original = JRT.toDouble(numObj);
1581 					double num = original - 1;
1582 					setNumOnJRT(fieldnum, num);
1583 
1584 					push(Double.valueOf(original));
1585 
1586 					position.next();
1587 					break;
1588 				}
1589 				case DEREFERENCE: {
1590 					// arg[0] = offset
1591 					// arg[1] = isGlobal
1592 					DereferenceTuple dereferenceTuple = (DereferenceTuple) tuple;
1593 					boolean isGlobal = dereferenceTuple.isGlobal();
1594 					long offset = dereferenceTuple.getVariableOffset();
1595 					Object o = runtimeStack.getVariable(offset, isGlobal);
1596 					if (o == null) {
1597 						if (dereferenceTuple.isArray()) {
1598 							// is_array
1599 							push(runtimeStack.setVariable(offset, newAwkArray(), isGlobal));
1600 						} else {
1601 							push(runtimeStack.setVariable(offset, BLANK, isGlobal));
1602 						}
1603 					} else {
1604 						push(o);
1605 					}
1606 					position.next();
1607 					break;
1608 				}
1609 				case DEREF_ARRAY: {
1610 					// stack[0] = array index
1611 					Object idx = pop(); // idx
1612 					checkScalar(idx);
1613 					Map<Object, Object> map = toMap(pop());
1614 					Object o = JRT.getAwkValue(map, idx);
1615 					push(o);
1616 					position.next();
1617 					break;
1618 				}
1619 				case ENSURE_ARRAY_ELEMENT: {
1620 					// stack[0] = array index
1621 					// stack[1] = associative array
1622 					Object idx = pop();
1623 					Map<Object, Object> map = toMap(pop());
1624 					push(ensureArrayInArray(map, idx));
1625 					position.next();
1626 					break;
1627 				}
1628 				case PEEK_ARRAY_ELEMENT: {
1629 					// stack[0] = array index
1630 					Object idx = pop();
1631 					checkScalar(idx);
1632 					Map<Object, Object> map = toMap(pop());
1633 					if (map instanceof AssocArray && !JRT.containsAwkKey(map, idx)) {
1634 						push(BLANK);
1635 					} else {
1636 						Object value = map.get(idx);
1637 						push(value != null ? value : BLANK);
1638 					}
1639 					position.next();
1640 					break;
1641 				}
1642 				case SRAND: {
1643 					// arg[0] = numArgs (where 0 = no args, anything else = one argument)
1644 					// stack[0] = seed (only if numArgs != 0)
1645 					CountTuple countTuple = (CountTuple) tuple;
1646 					long numArgs = countTuple.getCount();
1647 					int seed;
1648 					if (numArgs == 0) {
1649 						// use the time of day for the seed
1650 						seed = JRT.timeSeed();
1651 					} else {
1652 						Object o = pop();
1653 						if (o instanceof Double) {
1654 							seed = ((Double) o).intValue();
1655 						} else if (o instanceof Long) {
1656 							seed = ((Long) o).intValue();
1657 						} else if (o instanceof Integer) {
1658 							seed = ((Integer) o).intValue();
1659 						} else {
1660 							try {
1661 								seed = Integer.parseInt(o.toString());
1662 							} catch (NumberFormatException nfe) {
1663 								seed = 0;
1664 							}
1665 						}
1666 					}
1667 					randomNumberGenerator.setSeed(seed);
1668 					push(oldseed);
1669 					oldseed = seed;
1670 					position.next();
1671 					break;
1672 				}
1673 				case RAND: {
1674 					push(randomNumberGenerator.nextDouble());
1675 					position.next();
1676 					break;
1677 				}
1678 				case INTFUNC: {
1679 					// stack[0] = arg to int() function
1680 					push((long) JRT.toDouble(pop()));
1681 					position.next();
1682 					break;
1683 				}
1684 				case SQRT: {
1685 					// stack[0] = arg to sqrt() function
1686 					push(Math.sqrt(JRT.toDouble(pop())));
1687 					position.next();
1688 					break;
1689 				}
1690 				case LOG: {
1691 					// stack[0] = arg to log() function
1692 					push(Math.log(JRT.toDouble(pop())));
1693 					position.next();
1694 					break;
1695 				}
1696 				case EXP: {
1697 					// stack[0] = arg to exp() function
1698 					push(Math.exp(JRT.toDouble(pop())));
1699 					position.next();
1700 					break;
1701 				}
1702 				case SIN: {
1703 					// stack[0] = arg to sin() function
1704 					push(Math.sin(JRT.toDouble(pop())));
1705 					position.next();
1706 					break;
1707 				}
1708 				case COS: {
1709 					// stack[0] = arg to cos() function
1710 					push(Math.cos(JRT.toDouble(pop())));
1711 					position.next();
1712 					break;
1713 				}
1714 				case ATAN2: {
1715 					// stack[0] = 2nd arg to atan2() function
1716 					// stack[1] = 1st arg to atan2() function
1717 					double d2 = JRT.toDouble(pop());
1718 					double d1 = JRT.toDouble(pop());
1719 					push(Math.atan2(d1, d2));
1720 					position.next();
1721 					break;
1722 				}
1723 				case MATCH: {
1724 					execMatch();
1725 					position.next();
1726 					break;
1727 				}
1728 				case INDEX: {
1729 					// stack[0] = 2nd arg to index() function
1730 					// stack[1] = 1st arg to index() function
1731 					String s2 = jrt.toAwkString(pop());
1732 					String s1 = jrt.toAwkString(pop());
1733 					push(s1.indexOf(s2) + 1);
1734 					position.next();
1735 					break;
1736 				}
1737 				case SUB_FOR_DOLLAR_0: {
1738 					execSubForDollar0((BooleanTuple) tuple);
1739 					position.next();
1740 					break;
1741 				}
1742 				case SUB_FOR_DOLLAR_REFERENCE: {
1743 					execSubForDollarReference((BooleanTuple) tuple);
1744 					position.next();
1745 					break;
1746 				}
1747 				case SUB_FOR_VARIABLE: {
1748 					execSubForVariable((SubstitutionVariableTuple) tuple, position);
1749 					position.next();
1750 					break;
1751 				}
1752 				case SUB_FOR_ARRAY_REFERENCE: {
1753 					execSubForArrayReference((SubstitutionVariableTuple) tuple);
1754 					position.next();
1755 					break;
1756 				}
1757 				case SUB_FOR_MAP_REFERENCE: {
1758 					execSubForMapReference((BooleanTuple) tuple);
1759 					position.next();
1760 					break;
1761 				}
1762 				case SPLIT: {
1763 					execSplit((CountTuple) tuple, position);
1764 					position.next();
1765 					break;
1766 				}
1767 				case SUBSTR: {
1768 					execSubstr((CountTuple) tuple);
1769 					position.next();
1770 					break;
1771 				}
1772 				case TOLOWER: {
1773 					// stack[0] = string
1774 					push(jrt.toAwkString(pop()).toLowerCase());
1775 					position.next();
1776 					break;
1777 				}
1778 				case TOUPPER: {
1779 					// stack[0] = string
1780 					push(jrt.toAwkString(pop()).toUpperCase());
1781 					position.next();
1782 					break;
1783 				}
1784 				case SYSTEM: {
1785 					// stack[0] = command string
1786 					String s = jrt.toAwkString(pop());
1787 					push(jrt.jrtSystem(s));
1788 					position.next();
1789 					break;
1790 				}
1791 				case SWAP: {
1792 					// stack[0] = item1
1793 					// stack[1] = item2
1794 					Object o1 = pop();
1795 					Object o2 = pop();
1796 					push(o1);
1797 					push(o2);
1798 					position.next();
1799 					break;
1800 				}
1801 				case CMP_EQ: {
1802 					// stack[0] = item2
1803 					// stack[1] = item1
1804 					Object o2 = pop();
1805 					Object o1 = pop();
1806 					push(JRT.compare2(o1, o2, 0) ? ONE : ZERO);
1807 					position.next();
1808 					break;
1809 				}
1810 				case CMP_LT: {
1811 					// stack[0] = item2
1812 					// stack[1] = item1
1813 					Object o2 = pop();
1814 					Object o1 = pop();
1815 					push(JRT.compare2(o1, o2, -1) ? ONE : ZERO);
1816 					position.next();
1817 					break;
1818 				}
1819 				case CMP_GT: {
1820 					// stack[0] = item2
1821 					// stack[1] = item1
1822 					Object o2 = pop();
1823 					Object o1 = pop();
1824 					push(JRT.compare2(o1, o2, 1) ? ONE : ZERO);
1825 					position.next();
1826 					break;
1827 				}
1828 				case MATCHES: {
1829 					// stack[0] = item2
1830 					// stack[1] = item1
1831 					Object o2 = pop();
1832 					Object o1 = pop();
1833 					// use o1's string value
1834 					String s = o1.toString();
1835 					// assume o2 is a regexp
1836 					if (o2 instanceof Pattern) {
1837 						Pattern p = (Pattern) o2;
1838 						Matcher m = p.matcher(s);
1839 						// m.matches() matches the ENTIRE string
1840 						// m.find() is more appropriate
1841 						boolean result = m.find();
1842 						push(result ? 1 : 0);
1843 					} else {
1844 						String r = jrt.toAwkString(o2);
1845 						boolean result = Pattern.compile(r).matcher(s).find();
1846 						push(result ? 1 : 0);
1847 					}
1848 					position.next();
1849 					break;
1850 				}
1851 				case ADD: {
1852 					// stack[0] = item2
1853 					// stack[1] = item1
1854 					Object o2 = pop();
1855 					Object o1 = pop();
1856 					double d1 = JRT.toDouble(o1);
1857 					double d2 = JRT.toDouble(o2);
1858 					double ans = d1 + d2;
1859 					push(ans);
1860 					position.next();
1861 					break;
1862 				}
1863 				case SUBTRACT: {
1864 					// stack[0] = item2
1865 					// stack[1] = item1
1866 					Object o2 = pop();
1867 					Object o1 = pop();
1868 					double d1 = JRT.toDouble(o1);
1869 					double d2 = JRT.toDouble(o2);
1870 					double ans = d1 - d2;
1871 					push(ans);
1872 					position.next();
1873 					break;
1874 				}
1875 				case MULTIPLY: {
1876 					// stack[0] = item2
1877 					// stack[1] = item1
1878 					Object o2 = pop();
1879 					Object o1 = pop();
1880 					double d1 = JRT.toDouble(o1);
1881 					double d2 = JRT.toDouble(o2);
1882 					double ans = d1 * d2;
1883 					push(ans);
1884 					position.next();
1885 					break;
1886 				}
1887 				case DIVIDE: {
1888 					// stack[0] = item2
1889 					// stack[1] = item1
1890 					Object o2 = pop();
1891 					Object o1 = pop();
1892 					double d1 = JRT.toDouble(o1);
1893 					double d2 = JRT.toDouble(o2);
1894 					double ans = d1 / d2;
1895 					push(ans);
1896 					position.next();
1897 					break;
1898 				}
1899 				case MOD: {
1900 					// stack[0] = item2
1901 					// stack[1] = item1
1902 					Object o2 = pop();
1903 					Object o1 = pop();
1904 					double d1 = JRT.toDouble(o1);
1905 					double d2 = JRT.toDouble(o2);
1906 					double ans = d1 % d2;
1907 					push(ans);
1908 					position.next();
1909 					break;
1910 				}
1911 				case POW: {
1912 					// stack[0] = item2
1913 					// stack[1] = item1
1914 					Object o2 = pop();
1915 					Object o1 = pop();
1916 					double d1 = JRT.toDouble(o1);
1917 					double d2 = JRT.toDouble(o2);
1918 					double ans = Math.pow(d1, d2);
1919 					push(ans);
1920 					position.next();
1921 					break;
1922 				}
1923 				case DUP: {
1924 					// stack[0] = top of stack item
1925 					Object o = pop();
1926 					push(o);
1927 					push(o);
1928 					position.next();
1929 					break;
1930 				}
1931 				case KEYLIST: {
1932 					Object o = pop();
1933 					if (o == null || o instanceof UninitializedObject) {
1934 						push(new ArrayDeque<>());
1935 						position.next();
1936 						break;
1937 					}
1938 					if (!(o instanceof Map)) {
1939 						throw new AwkRuntimeException(
1940 								position.lineNumber(),
1941 								"Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
1942 					}
1943 					@SuppressWarnings("unchecked")
1944 					Map<Object, Object> map = (Map<Object, Object>) o;
1945 					push(new ArrayDeque<>(map.keySet()));
1946 					position.next();
1947 					break;
1948 				}
1949 				case IS_EMPTY_KEYLIST: {
1950 					// arg[0] = address
1951 					// stack[0] = Deque
1952 					Object o = pop();
1953 					if (o == null || !(o instanceof Deque)) {
1954 						throw new AwkRuntimeException(
1955 								position.lineNumber(),
1956 								"Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
1957 					}
1958 					Deque<?> keylist = (Deque<?>) o;
1959 					if (keylist.isEmpty()) {
1960 						position.jump(tuple.getAddress());
1961 					} else {
1962 						position.next();
1963 					}
1964 					break;
1965 				}
1966 				case GET_FIRST_AND_REMOVE_FROM_KEYLIST: {
1967 					// stack[0] = Deque
1968 					Object o = pop();
1969 					if (o == null || !(o instanceof Deque)) {
1970 						throw new AwkRuntimeException(
1971 								position.lineNumber(),
1972 								"Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
1973 					}
1974 					// pop off and return the head of the key set
1975 					Deque<?> keylist = (Deque<?>) o;
1976 					push(keylist.removeFirst());
1977 					position.next();
1978 					break;
1979 				}
1980 				case CHECK_CLASS: {
1981 					// arg[0] = class object
1982 					// stack[0] = item to check
1983 					ClassTuple checkTuple = (ClassTuple) tuple;
1984 					Object o = pop();
1985 					if (!checkTuple.getType().isInstance(o)) {
1986 						throw new AwkRuntimeException(
1987 								position.lineNumber(),
1988 								"Verification failed. Top-of-stack = " + o.getClass() + " isn't an instance of "
1989 										+ checkTuple.getType());
1990 					}
1991 					push(o);
1992 					position.next();
1993 					break;
1994 				}
1995 				case CONSUME_INPUT: {
1996 					// arg[0] = address
1997 					// store the next record into $0, $1, ...
1998 					applyInputSourceFilelistAssignmentsIfNeeded();
1999 					if (jrt.consumeInput(resolvedInputSource)) {
2000 						position.next();
2001 					} else {
2002 						position.jump(tuple.getAddress());
2003 					}
2004 					break;
2005 				}
2006 
2007 				case GETLINE_INPUT: {
2008 					applyInputSourceFilelistAssignmentsIfNeeded();
2009 					push(jrt.consumeInput(resolvedInputSource) ? 1 : 0);
2010 					position.next();
2011 					break;
2012 				}
2013 				case GETLINE_INPUT_TO_TARGET: {
2014 					applyInputSourceFilelistAssignmentsIfNeeded();
2015 					Object input = jrt.consumeInputToTarget(resolvedInputSource);
2016 					if (input != null) {
2017 						push(1);
2018 						push(input);
2019 					} else {
2020 						push(0);
2021 						push("");
2022 					}
2023 					position.next();
2024 					break;
2025 				}
2026 				case USE_AS_FILE_INPUT: {
2027 					// stack[0] = filename
2028 					String s = jrt.toAwkString(pop());
2029 					if (jrt.jrtConsumeFileInput(s)) {
2030 						push(1);
2031 						push(jrt.getInputLine());
2032 					} else {
2033 						push(0);
2034 						push("");
2035 					}
2036 					position.next();
2037 					break;
2038 				}
2039 				case USE_AS_COMMAND_INPUT: {
2040 					// stack[0] = command line
2041 					String s = jrt.toAwkString(pop());
2042 					if (jrt.jrtConsumeCommandInput(s)) {
2043 						push(1);
2044 						push(jrt.getInputLine());
2045 					} else {
2046 						push(0);
2047 						push("");
2048 					}
2049 					position.next();
2050 					break;
2051 				}
2052 				case ENVIRON_OFFSET: {
2053 					// stack[0] = offset
2054 					//// assignArray(offset, arrIdx, newstring, isGlobal);
2055 					LongTuple offsetTuple = (LongTuple) tuple;
2056 					environOffset = offsetTuple.getValue();
2057 					// set the initial variables
2058 					Map<String, String> env = System.getenv();
2059 					for (Map.Entry<String, String> var : env.entrySet()) {
2060 						assignArray(environOffset, var.getKey(), jrt.toInputScalar(var.getValue()), true);
2061 						pop(); // clean up the stack after the assignment
2062 					}
2063 					position.next();
2064 					break;
2065 				}
2066 				case ARGC_OFFSET: {
2067 					// stack[0] = offset
2068 					LongTuple offsetTuple = (LongTuple) tuple;
2069 					argcOffset = offsetTuple.getValue();
2070 					// assign(argcOffset, arguments.size(), true, position); // true = global
2071 					// +1 to include the "jawk" program name (ARGV[0])
2072 					assign(argcOffset, arguments.size() + 1, true, position, false); // true = global
2073 					position.next();
2074 					break;
2075 				}
2076 				case ARGV_OFFSET: {
2077 					// stack[0] = offset
2078 					LongTuple offsetTuple = (LongTuple) tuple;
2079 					argvOffset = offsetTuple.getValue();
2080 					// consume argv (looping from 1 to argc)
2081 					int argc = (int) JRT.toDouble(runtimeStack.getVariable(argcOffset, true)); // true = global
2082 					assignArray(argvOffset, 0, "jawk", true);
2083 					pop();
2084 					for (int i = 1; i < argc; i++) {
2085 						// assignArray(argvOffset, i+1, arguments.get(i), true);
2086 						assignArray(argvOffset, i, jrt.toInputScalar(arguments.get(i - 1)), true);
2087 						pop(); // clean up the stack after the assignment
2088 					}
2089 					position.next();
2090 					break;
2091 				}
2092 				case GET_INPUT_FIELD: {
2093 					// stack[0] = field number
2094 					Object fieldNumber = pop();
2095 					push(jrt.jrtGetInputField(fieldNumber));
2096 					position.next();
2097 					break;
2098 				}
2099 				case GET_INPUT_FIELD_CONST: {
2100 					InputFieldTuple inputFieldTuple = (InputFieldTuple) tuple;
2101 					long fieldnum = inputFieldTuple.getFieldIndex();
2102 					push(jrt.jrtGetInputField(fieldnum));
2103 					position.next();
2104 					break;
2105 				}
2106 				case APPLY_RS: {
2107 					jrt.applyRS(jrt.getRSVar());
2108 					position.next();
2109 					break;
2110 				}
2111 				case CALL_FUNCTION: {
2112 					// arg[0] = function address
2113 					// arg[1] = function name
2114 					// arg[2] = # of formal parameters
2115 					// arg[3] = # of actual parameters
2116 					// stack[0] = last actual parameter
2117 					// stack[1] = before-last actual parameter
2118 					// ...
2119 					// stack[n-1] = first actual parameter
2120 					// etc.
2121 					CallFunctionTuple callTuple = (CallFunctionTuple) tuple;
2122 					Address funcAddr = callTuple.getAddress();
2123 					long numFormalParams = callTuple.getNumFormalParams();
2124 					long numActualParams = callTuple.getNumActualParams();
2125 					runtimeStack.pushFrame(numFormalParams, position.currentIndex());
2126 					// Arguments are stacked, so first in the stack is the last for the function
2127 					for (long i = numActualParams - 1; i >= 0; i--) {
2128 						runtimeStack.setVariable(i, pop(), false); // false = local
2129 					}
2130 					position.jump(funcAddr);
2131 					// position.next();
2132 					break;
2133 				}
2134 				case FUNCTION: {
2135 					// important for compilation,
2136 					// not needed for interpretation
2137 					// arg[0] = function name
2138 					// arg[1] = # of formal parameters
2139 					position.next();
2140 					break;
2141 				}
2142 				case SET_RETURN_RESULT: {
2143 					// stack[0] = return result
2144 					runtimeStack.setReturnValue(pop());
2145 					position.next();
2146 					break;
2147 				}
2148 				case RETURN_FROM_FUNCTION: {
2149 					position.jump(runtimeStack.popFrame());
2150 					push(runtimeStack.getReturnValue());
2151 					position.next();
2152 					break;
2153 				}
2154 				case SET_NUM_GLOBALS: {
2155 					execSetNumGlobals((CountTuple) tuple);
2156 					position.next();
2157 					break;
2158 				}
2159 				case CLOSE: {
2160 					// stack[0] = file or command line to close
2161 					String s = jrt.toAwkString(pop());
2162 					push(jrt.jrtClose(s));
2163 					position.next();
2164 					break;
2165 				}
2166 				case APPLY_SUBSEP: {
2167 					execApplySubsep((CountTuple) tuple);
2168 					position.next();
2169 					break;
2170 				}
2171 				case DELETE_ARRAY_ELEMENT: {
2172 					// arg[0] = offset
2173 					// arg[1] = isGlobal
2174 					// stack[0] = array index
2175 					VariableTuple variableTuple = (VariableTuple) tuple;
2176 					long offset = variableTuple.getVariableOffset();
2177 					boolean isGlobal = variableTuple.isGlobal();
2178 					Map<Object, Object> aa = getMapVariable(offset, isGlobal);
2179 					Object key = pop();
2180 					checkScalar(key);
2181 					if (aa != null) {
2182 						aa.remove(key);
2183 					}
2184 					position.next();
2185 					break;
2186 				}
2187 				case DELETE_MAP_ELEMENT: {
2188 					// stack[0] = array index
2189 					// stack[1] = associative array
2190 					Object key = pop();
2191 					checkScalar(key);
2192 					Map<Object, Object> aa = toMap(pop());
2193 					aa.remove(key);
2194 					position.next();
2195 					break;
2196 				}
2197 				case DELETE_ARRAY: {
2198 					// arg[0] = offset
2199 					// arg[1] = isGlobal
2200 					// (nothing on the stack)
2201 					VariableTuple variableTuple = (VariableTuple) tuple;
2202 					long offset = variableTuple.getVariableOffset();
2203 					boolean isGlobal = variableTuple.isGlobal();
2204 					Map<Object, Object> array = getMapVariable(offset, isGlobal);
2205 					if (array != null) {
2206 						array.clear();
2207 					}
2208 					position.next();
2209 					break;
2210 				}
2211 				case SET_EXIT_ADDRESS: {
2212 					// arg[0] = exit address
2213 					exitAddress = tuple.getAddress();
2214 					position.next();
2215 					break;
2216 				}
2217 				case SET_WITHIN_END_BLOCKS: {
2218 					// arg[0] = whether within the END blocks section
2219 					BooleanTuple endBlocksTuple = (BooleanTuple) tuple;
2220 					withinEndBlocks = endBlocksTuple.getValue();
2221 					position.next();
2222 					break;
2223 				}
2224 				case EXIT_WITHOUT_CODE:
2225 				case EXIT_WITH_CODE: {
2226 					if (opcode == Opcode.EXIT_WITH_CODE) {
2227 						// stack[0] = exit code
2228 						exitCode = (int) JRT.toDouble(pop());
2229 					}
2230 					throwExitException = true;
2231 
2232 					// If in BEGIN or in a rule, jump to the END section
2233 					if (!withinEndBlocks && exitAddress != null) {
2234 						// clear runtime stack
2235 						runtimeStack.popAllFrames();
2236 						// clear operand stack
2237 						operandStack.clear();
2238 						position.jump(exitAddress);
2239 					} else {
2240 						// Exit immediately with ExitException
2241 						// clear operand stack
2242 						operandStack.clear();
2243 						throw new ExitException(exitCode, "The AWK script requested an exit");
2244 						// position.next();
2245 					}
2246 					break;
2247 				}
2248 				case REGEXP: {
2249 					// Literal regex tuples must provide a precompiled Pattern as arg[1]
2250 					RegexTuple regexTuple = (RegexTuple) tuple;
2251 					Pattern pattern = regexTuple.getPattern();
2252 					push(pattern);
2253 					position.next();
2254 					break;
2255 				}
2256 				case CONDITION_PAIR: {
2257 					// stack[0] = End condition
2258 					// stack[1] = Start condition
2259 					if (conditionPairs == null) {
2260 						conditionPairs = new HashMap<Integer, ConditionPair>();
2261 					}
2262 					int currentIndex = position.currentIndex();
2263 					ConditionPair cp = conditionPairs.get(currentIndex);
2264 					if (cp == null) {
2265 						cp = new ConditionPair();
2266 						conditionPairs.put(currentIndex, cp);
2267 					}
2268 					boolean end = jrt.toBoolean(pop());
2269 					boolean start = jrt.toBoolean(pop());
2270 					push(cp.update(start, end) ? ONE : ZERO);
2271 					position.next();
2272 					break;
2273 				}
2274 				case IS_IN: {
2275 					// stack[1] = key to check
2276 					Object arr = pop();
2277 					Object arg = pop();
2278 					checkScalar(arg);
2279 					if (arr == null || arr instanceof UninitializedObject) {
2280 						push(ZERO);
2281 						position.next();
2282 						break;
2283 					}
2284 					if (!(arr instanceof Map)) {
2285 						throw new AwkRuntimeException("Attempting to test membership on a non-associative-array.");
2286 					}
2287 					@SuppressWarnings("unchecked")
2288 					Map<Object, Object> aa = (Map<Object, Object>) arr;
2289 					boolean result = JRT.containsAwkKey(aa, arg);
2290 					push(result ? ONE : ZERO);
2291 					position.next();
2292 					break;
2293 				}
2294 				case THIS: {
2295 					// this is in preparation for a function
2296 					// call for the JVM-COMPILED script, only
2297 					// therefore, do NOTHING for the interpreted
2298 					// version
2299 					position.next();
2300 					break;
2301 				}
2302 				case EXTENSION: {
2303 					// arg[0] = extension function metadata
2304 					// arg[1] = # of args on the stack
2305 					// arg[2] = true if parent is NOT an extension function call
2306 					// (i.e., initial extension in calling expression)
2307 					// stack[0] = first actual parameter
2308 					// stack[1] = second actual parameter
2309 					// etc.
2310 					ExtensionTuple extensionTuple = (ExtensionTuple) tuple;
2311 					ExtensionFunction function = extensionTuple.getFunction();
2312 					long numArgs = extensionTuple.getArgCount();
2313 					boolean isInitial = extensionTuple.isInitial();
2314 
2315 					Object[] args = new Object[(int) numArgs];
2316 					for (int i = (int) numArgs - 1; i >= 0; i--) {
2317 						args[i] = pop();
2318 					}
2319 
2320 					String extensionClassName = function.getExtensionClassName();
2321 					JawkExtension extension = extensionInstances.get(extensionClassName);
2322 					if (extension == null) {
2323 						throw new AwkRuntimeException(
2324 								position.lineNumber(),
2325 								"Extension instance for class '" + extensionClassName
2326 										+ "' is not registered");
2327 					}
2328 					if (!(extension instanceof AbstractExtension)) {
2329 						throw new AwkRuntimeException(
2330 								position.lineNumber(),
2331 								"Extension instance for class '" + extensionClassName
2332 										+ "' does not extend "
2333 										+ AbstractExtension.class.getName());
2334 					}
2335 
2336 					Object retval = function.invoke((AbstractExtension) extension, args);
2337 
2338 					// block if necessary
2339 					// (convert retval into the return value
2340 					// from the block operation ...)
2341 					if (isInitial && retval != null && retval instanceof BlockObject) {
2342 						retval = new BlockManager().block((BlockObject) retval);
2343 					}
2344 					// (... and proceed)
2345 
2346 					if (retval == null) {
2347 						retval = "";
2348 					} else
2349 						if (!(retval instanceof Integer
2350 								||
2351 								retval instanceof Long
2352 								||
2353 								retval instanceof Double
2354 								||
2355 								retval instanceof String
2356 								||
2357 								retval instanceof Map
2358 								||
2359 								retval instanceof BlockObject)) {
2360 									// all other extension results are converted
2361 									// to a string (via Object.toString())
2362 									retval = retval.toString();
2363 								}
2364 					push(retval);
2365 
2366 					position.next();
2367 					break;
2368 				}
2369 				case ASSIGN_NF: {
2370 					Object v = pop();
2371 					jrt.setNF(v);
2372 					push(v);
2373 					position.next();
2374 					break;
2375 				}
2376 				case PUSH_NF: {
2377 					push(jrt.getNF());
2378 					position.next();
2379 					break;
2380 				}
2381 				case ASSIGN_NR: {
2382 					Object v = pop();
2383 					jrt.setNR(v);
2384 					push(v);
2385 					position.next();
2386 					break;
2387 				}
2388 				case PUSH_NR: {
2389 					push(jrt.getNR());
2390 					position.next();
2391 					break;
2392 				}
2393 				case ASSIGN_FNR: {
2394 					Object v = pop();
2395 					jrt.setFNR(v);
2396 					push(v);
2397 					position.next();
2398 					break;
2399 				}
2400 				case PUSH_FNR: {
2401 					push(jrt.getFNR());
2402 					position.next();
2403 					break;
2404 				}
2405 				case ASSIGN_FS: {
2406 					Object v = pop();
2407 					jrt.setFS(v);
2408 					push(v);
2409 					position.next();
2410 					break;
2411 				}
2412 				case PUSH_FS: {
2413 					push(jrt.getFSVar());
2414 					position.next();
2415 					break;
2416 				}
2417 				case ASSIGN_RS: {
2418 					Object v = pop();
2419 					jrt.setRS(v);
2420 					push(v);
2421 					position.next();
2422 					break;
2423 				}
2424 				case PUSH_RS: {
2425 					push(jrt.getRSVar());
2426 					position.next();
2427 					break;
2428 				}
2429 				case ASSIGN_OFS: {
2430 					Object v = pop();
2431 					jrt.setOFS(v);
2432 					push(v);
2433 					position.next();
2434 					break;
2435 				}
2436 				case PUSH_OFS: {
2437 					push(jrt.getOFSVar());
2438 					position.next();
2439 					break;
2440 				}
2441 				case ASSIGN_ORS: {
2442 					Object v = pop();
2443 					jrt.setORS(v);
2444 					push(v);
2445 					position.next();
2446 					break;
2447 				}
2448 				case PUSH_ORS: {
2449 					push(jrt.getORSVar());
2450 					position.next();
2451 					break;
2452 				}
2453 				case ASSIGN_RSTART: {
2454 					Object v = pop();
2455 					jrt.setRSTART(v);
2456 					push(v);
2457 					position.next();
2458 					break;
2459 				}
2460 				case PUSH_RSTART: {
2461 					push(jrt.getRSTART());
2462 					position.next();
2463 					break;
2464 				}
2465 				case ASSIGN_RLENGTH: {
2466 					Object v = pop();
2467 					jrt.setRLENGTH(v);
2468 					push(v);
2469 					position.next();
2470 					break;
2471 				}
2472 				case PUSH_RLENGTH: {
2473 					push(jrt.getRLENGTH());
2474 					position.next();
2475 					break;
2476 				}
2477 				case ASSIGN_FILENAME: {
2478 					Object v = pop();
2479 					jrt.setFILENAMEViaJrt(v);
2480 					push(v == null ? "" : v);
2481 					position.next();
2482 					break;
2483 				}
2484 				case PUSH_FILENAME: {
2485 					push(jrt.getFILENAME());
2486 					position.next();
2487 					break;
2488 				}
2489 				case ASSIGN_SUBSEP: {
2490 					Object v = pop();
2491 					jrt.setSUBSEP(v);
2492 					push(v);
2493 					position.next();
2494 					break;
2495 				}
2496 				case PUSH_SUBSEP: {
2497 					push(jrt.getSUBSEPVar());
2498 					position.next();
2499 					break;
2500 				}
2501 				case ASSIGN_CONVFMT: {
2502 					Object v = pop();
2503 					jrt.setCONVFMT(v);
2504 					push(v);
2505 					position.next();
2506 					break;
2507 				}
2508 				case PUSH_CONVFMT: {
2509 					push(jrt.getCONVFMTVar());
2510 					position.next();
2511 					break;
2512 				}
2513 				case ASSIGN_OFMT: {
2514 					Object v = pop();
2515 					jrt.setOFMT(v);
2516 					push(v);
2517 					position.next();
2518 					break;
2519 				}
2520 				case PUSH_OFMT: {
2521 					push(getOFMT());
2522 					position.next();
2523 					break;
2524 				}
2525 				case ASSIGN_ARGC: {
2526 					Object v = pop();
2527 					if (argcOffset == NULL_OFFSET) {
2528 						throw new AwkRuntimeException("ARGC is read-only (not materialized).");
2529 					}
2530 					runtimeStack.setVariable(argcOffset, v, true);
2531 					push(v);
2532 					position.next();
2533 					break;
2534 				}
2535 				case PUSH_ARGC: {
2536 					if (argcOffset == NULL_OFFSET) {
2537 						push(getARGC());
2538 					} else {
2539 						push(runtimeStack.getVariable(argcOffset, true));
2540 					}
2541 					position.next();
2542 					break;
2543 				}
2544 				default:
2545 					throw new Error("invalid opcode: " + position.opcode());
2546 				}
2547 				if (profiling) {
2548 					afterProfiledTuple(opcode, tupleStartNanos);
2549 				}
2550 			}
2551 
2552 		} catch (ExitException ee) {
2553 			if (profiling && (opcode == Opcode.EXIT_WITH_CODE || opcode == Opcode.EXIT_WITHOUT_CODE)) {
2554 				afterProfiledTuple(opcode, tupleStartNanos);
2555 			}
2556 			throw ee;
2557 		} catch (IOException ioe) {
2558 			// clear runtime stack
2559 			runtimeStack.popAllFrames();
2560 			// clear operand stack
2561 			operandStack.clear();
2562 			throw ioe;
2563 		} catch (RuntimeException re) {
2564 			// clear runtime stack
2565 			runtimeStack.popAllFrames();
2566 			// clear operand stack
2567 			operandStack.clear();
2568 			if (re instanceof AwkSandboxException) {
2569 				throw re;
2570 			}
2571 			throw new AwkRuntimeException(position.lineNumber(), re.getMessage(), re);
2572 		} catch (AssertionError ae) {
2573 			// clear runtime stack
2574 			runtimeStack.popAllFrames();
2575 			// clear operand stack
2576 			operandStack.clear();
2577 			throw ae;
2578 		}
2579 
2580 		// If <code>exit</code> was called, throw an ExitException
2581 		if (throwExitException) {
2582 			throw new ExitException(exitCode, "The AWK script requested an exit");
2583 		}
2584 	}
2585 
2586 	/**
2587 	 * Clears all collected profiling statistics.
2588 	 */
2589 	public void resetProfiling() {
2590 		if (!profiling) {
2591 			return;
2592 		}
2593 		tupleProfilingStats.clear();
2594 		functionProfilingStats.clear();
2595 		activeProfilingFunctions.clear();
2596 	}
2597 
2598 	/**
2599 	 * Returns an immutable snapshot of the collected profiling statistics.
2600 	 *
2601 	 * @return profiling report snapshot
2602 	 */
2603 	public ProfilingReport getProfilingReport() {
2604 		if (!profiling) {
2605 			return ProfilingReport.empty();
2606 		}
2607 		return new ProfilingReport(tupleProfilingStats, functionProfilingStats);
2608 	}
2609 
2610 	private void execPrint(CountTuple tuple) throws IOException {
2611 		long numArgs = tuple.getCount();
2612 		jrt.printDefault(numArgs == 0 ? new Object[] { jrt.jrtGetInputField(0) } : popArguments(numArgs));
2613 	}
2614 
2615 	private void execPrintToFile(CountAndAppendTuple tuple) throws IOException {
2616 		String key = jrt.toAwkString(pop());
2617 		long numArgs = tuple.getCount();
2618 		jrt
2619 				.printToFile(
2620 						key,
2621 						tuple.isAppend(),
2622 						numArgs == 0 ? new Object[]
2623 						{ jrt.jrtGetInputField(0) } : popArguments(numArgs));
2624 	}
2625 
2626 	private void execPrintToPipe(CountTuple tuple) throws IOException {
2627 		String cmd = jrt.toAwkString(pop());
2628 		long numArgs = tuple.getCount();
2629 		jrt.printToProcess(cmd, numArgs == 0 ? new Object[] { jrt.jrtGetInputField(0) } : popArguments(numArgs));
2630 	}
2631 
2632 	private void execPrintf(CountTuple tuple) throws IOException {
2633 		long numArgs = tuple.getCount();
2634 		Object[] values = popArguments(numArgs - 1);
2635 		String format = jrt.toAwkString(pop());
2636 		jrt.printfDefault(format, values);
2637 	}
2638 
2639 	private void execPrintfToFile(CountAndAppendTuple tuple) throws IOException {
2640 		String key = jrt.toAwkString(pop());
2641 		long numArgs = tuple.getCount();
2642 		Object[] values = popArguments(numArgs - 1);
2643 		String format = jrt.toAwkString(pop());
2644 		jrt.printfToFile(key, tuple.isAppend(), format, values);
2645 	}
2646 
2647 	private void execPrintfToPipe(CountTuple tuple) throws IOException {
2648 		String cmd = jrt.toAwkString(pop());
2649 		long numArgs = tuple.getCount();
2650 		Object[] values = popArguments(numArgs - 1);
2651 		String format = jrt.toAwkString(pop());
2652 		jrt.printfToProcess(cmd, format, values);
2653 	}
2654 
2655 	private void execLength(CountTuple tuple) {
2656 		long num = tuple.getCount();
2657 		if (num == 0) {
2658 			push(jrt.jrtGetInputField(0).toString().length());
2659 			return;
2660 		}
2661 		Object value = pop();
2662 		if (value instanceof Map) {
2663 			push((long) ((Map<?, ?>) value).size());
2664 		} else {
2665 			push(jrt.toAwkString(value).length());
2666 		}
2667 	}
2668 
2669 	private void execMatch() {
2670 		String ere = jrt.toAwkString(pop());
2671 		String s = jrt.toAwkString(pop());
2672 		int flags = 0;
2673 		if (globalVariableOffsets.containsKey("IGNORECASE")) {
2674 			Integer offsetObj = globalVariableOffsets.get("IGNORECASE");
2675 			Object ignorecase = runtimeStack.getVariable(offsetObj, true);
2676 			if (JRT.toDouble(ignorecase) != 0) {
2677 				flags |= Pattern.CASE_INSENSITIVE;
2678 			}
2679 		}
2680 		Pattern pattern = Pattern.compile(ere, flags);
2681 		Matcher matcher = pattern.matcher(s);
2682 		if (matcher.find()) {
2683 			int start = matcher.start() + 1;
2684 			int len = matcher.end() - matcher.start();
2685 			jrt.setRSTART(start);
2686 			jrt.setRLENGTH(len);
2687 			push(start);
2688 		} else {
2689 			jrt.setRSTART(0);
2690 			jrt.setRLENGTH(-1);
2691 			push(0);
2692 		}
2693 	}
2694 
2695 	private void execSubForDollar0(BooleanTuple tuple) {
2696 		boolean isGsub = tuple.getValue();
2697 		String repl = jrt.toAwkString(pop());
2698 		String ere = jrt.toAwkString(pop());
2699 		String orig = jrt.toAwkString(jrt.jrtGetInputField(0));
2700 		String newstring = isGsub ? replaceAll(orig, ere, repl) : replaceFirst(orig, ere, repl);
2701 		jrt.setInputLine(newstring);
2702 		jrt.jrtParseFields();
2703 	}
2704 
2705 	private void execSubForDollarReference(BooleanTuple tuple) {
2706 		boolean isGsub = tuple.getValue();
2707 		long fieldNum = JRT.parseFieldNumber(pop());
2708 		String orig = jrt.toAwkString(pop());
2709 		String repl = jrt.toAwkString(pop());
2710 		String ere = jrt.toAwkString(pop());
2711 		String newstring = isGsub ? replaceAll(orig, ere, repl) : replaceFirst(orig, ere, repl);
2712 		if (fieldNum == 0) {
2713 			jrt.setInputLine(newstring);
2714 			jrt.jrtParseFields();
2715 		} else {
2716 			jrt.jrtSetInputField(newstring, fieldNum);
2717 		}
2718 	}
2719 
2720 	private void execSubForVariable(SubstitutionVariableTuple tuple, PositionTracker position) {
2721 		String newString = execSubOrGSub(tuple.isGlobalSubstitution());
2722 		assign(tuple.getVariableOffset(), newString, tuple.isGlobal(), position, false);
2723 	}
2724 
2725 	private void execSubForArrayReference(SubstitutionVariableTuple tuple) {
2726 		Object arrIdx = pop();
2727 		String newString = execSubOrGSub(tuple.isGlobalSubstitution());
2728 		assignArray(tuple.getVariableOffset(), arrIdx, newString, tuple.isGlobal());
2729 		pop();
2730 	}
2731 
2732 	private void execSubForMapReference(BooleanTuple tuple) {
2733 		Object arrIdx = pop();
2734 		Map<Object, Object> array = toMap(pop());
2735 		String newString = execSubOrGSub(tuple.getValue());
2736 		assignMapElement(array, arrIdx, newString);
2737 		pop();
2738 	}
2739 
2740 	private void execSplit(CountTuple tuple, PositionTracker position) {
2741 		long numArgs = tuple.getCount();
2742 		String fsString;
2743 		if (numArgs == 2) {
2744 			fsString = jrt.toAwkString(jrt.getFSVar());
2745 		} else if (numArgs == 3) {
2746 			fsString = jrt.toAwkString(pop());
2747 		} else {
2748 			throw new Error("Invalid # of args. split() requires 2 or 3. Got: " + numArgs);
2749 		}
2750 		Object o = pop();
2751 		if (!(o instanceof Map)) {
2752 			throw new AwkRuntimeException(position.lineNumber(), o + " is not an array.");
2753 		}
2754 		String s = jrt.toAwkString(pop());
2755 		Enumeration<Object> tokenizer;
2756 		if (fsString.equals(" ")) {
2757 			tokenizer = new StringTokenizer(s);
2758 		} else if (fsString.length() == 1) {
2759 			tokenizer = new SingleCharacterTokenizer(s, fsString.charAt(0));
2760 		} else if (fsString.isEmpty()) {
2761 			tokenizer = new CharacterTokenizer(s);
2762 		} else {
2763 			tokenizer = new RegexTokenizer(s, fsString);
2764 		}
2765 
2766 		@SuppressWarnings("unchecked")
2767 		Map<Object, Object> assocArray = (Map<Object, Object>) o;
2768 		assocArray.clear();
2769 		long cnt = 0;
2770 		while (tokenizer.hasMoreElements()) {
2771 			Object value = tokenizer.nextElement();
2772 			assocArray.put(++cnt, jrt.toInputScalar(value));
2773 		}
2774 		push(cnt);
2775 	}
2776 
2777 	private void execSubstr(CountTuple tuple) {
2778 		long numArgs = tuple.getCount();
2779 		int startPos, length;
2780 		String s;
2781 		if (numArgs == 3) {
2782 			length = (int) JRT.toLong(pop());
2783 			startPos = (int) JRT.toDouble(pop());
2784 			s = jrt.toAwkString(pop());
2785 		} else if (numArgs == 2) {
2786 			startPos = (int) JRT.toDouble(pop());
2787 			s = jrt.toAwkString(pop());
2788 			length = s.length() - startPos + 1;
2789 		} else {
2790 			throw new Error("numArgs for SUBSTR must be 2 or 3. It is " + numArgs);
2791 		}
2792 		if (startPos <= 0) {
2793 			startPos = 1;
2794 		}
2795 		if (length <= 0 || startPos > s.length()) {
2796 			push(BLANK);
2797 		} else if (startPos + length > s.length()) {
2798 			push(s.substring(startPos - 1));
2799 		} else {
2800 			push(s.substring(startPos - 1, startPos + length - 1));
2801 		}
2802 	}
2803 
2804 	private void execSetNumGlobals(CountTuple tuple) {
2805 		long numGlobals = tuple.getCount();
2806 		Object[] globals = runtimeStack.getNumGlobals();
2807 		if (mergedGlobalLayoutActive) {
2808 			if (!hasCompatiblePersistentGlobalLayout(numGlobals)) {
2809 				throw new IllegalStateException(
2810 						"AVM globals are already initialized for an incompatible persistent layout.");
2811 			}
2812 			applyExecutionInitialVariablesToGlobalSlots(true);
2813 		} else if (globals == null) {
2814 			runtimeStack.setNumGlobals(numGlobals, globalVariableOffsets);
2815 			initializedEvalGlobalVariableOffsets = globalVariableOffsets;
2816 			initializedEvalGlobalVariableArrays = globalVariableArrays;
2817 			applyExecutionInitialVariablesToGlobalSlots(false);
2818 		} else if (!hasCompatibleEvalGlobalLayout(numGlobals)) {
2819 			throw new IllegalStateException(
2820 					"AVM globals are already initialized for a different eval layout. Call prepareForEval(...) first.");
2821 		}
2822 	}
2823 
2824 	private void execApplySubsep(CountTuple tuple) {
2825 		long count = tuple.getCount();
2826 		if (count == 1) {
2827 			Object value = pop();
2828 			checkScalar(value);
2829 			push(jrt.toAwkString(value));
2830 			return;
2831 		}
2832 		StringBuilder sb = new StringBuilder();
2833 		Object value = pop();
2834 		checkScalar(value);
2835 		sb.append(jrt.toAwkString(value));
2836 		String subsep = jrt.toAwkString(jrt.getSUBSEPVar());
2837 		for (int i = 1; i < count; i++) {
2838 			sb.insert(0, subsep);
2839 			value = pop();
2840 			checkScalar(value);
2841 			sb.insert(0, jrt.toAwkString(value));
2842 		}
2843 		push(sb.toString());
2844 	}
2845 
2846 	private long beforeProfiledTuple(Tuple tuple, Opcode opcode) {
2847 		long now = System.nanoTime();
2848 		if (opcode == Opcode.CALL_FUNCTION) {
2849 			CallFunctionTuple callTuple = (CallFunctionTuple) tuple;
2850 			activeProfilingFunctions.push(new ActiveFunction(callTuple.getFunctionName(), now));
2851 		} else if (opcode == Opcode.EXTENSION) {
2852 			ExtensionTuple extensionTuple = (ExtensionTuple) tuple;
2853 			ExtensionFunction function = extensionTuple.getFunction();
2854 			activeProfilingFunctions.push(new ActiveFunction(function.getKeyword(), now));
2855 		}
2856 		return now;
2857 	}
2858 
2859 	private void afterProfiledTuple(Opcode opcode, long tupleStartNanos) {
2860 		long now = System.nanoTime();
2861 		statisticsFor(tupleProfilingStats, opcode).add(now - tupleStartNanos);
2862 		if (opcode == Opcode.EXIT_WITH_CODE || opcode == Opcode.EXIT_WITHOUT_CODE) {
2863 			recordAllFunctionExits(now);
2864 		} else if (opcode == Opcode.EXTENSION || opcode == Opcode.RETURN_FROM_FUNCTION) {
2865 			recordFunctionExit(now);
2866 		}
2867 	}
2868 
2869 	private static <K> ProfilingReport.Accumulator statisticsFor(
2870 			Map<K, ProfilingReport.Accumulator> stats,
2871 			K key) {
2872 		ProfilingReport.Accumulator accumulator = stats.get(key);
2873 		if (accumulator == null) {
2874 			accumulator = new ProfilingReport.Accumulator();
2875 			stats.put(key, accumulator);
2876 		}
2877 		return accumulator;
2878 	}
2879 
2880 	private void recordFunctionExit(long now) {
2881 		if (activeProfilingFunctions.isEmpty()) {
2882 			return;
2883 		}
2884 		ActiveFunction function = activeProfilingFunctions.pop();
2885 		statisticsFor(functionProfilingStats, function.name).add(now - function.startNanos);
2886 	}
2887 
2888 	private void recordAllFunctionExits(long now) {
2889 		while (!activeProfilingFunctions.isEmpty()) {
2890 			recordFunctionExit(now);
2891 		}
2892 	}
2893 
2894 	private static final class ActiveFunction {
2895 		private final String name;
2896 		private final long startNanos;
2897 
2898 		private ActiveFunction(String name, long startNanos) {
2899 			this.name = name;
2900 			this.startNanos = startNanos;
2901 		}
2902 	}
2903 
2904 	/**
2905 	 * Releases any prepared input source and runtime I/O resources owned by this
2906 	 * AVM.
2907 	 * <p>
2908 	 * Call this when you are done with an AVM obtained through expert-level
2909 	 * integration, or after direct {@link #eval(AwkExpression, InputSource)} /
2910 	 * {@link #execute(AwkProgram, InputSource)} usage.
2911 	 * The AVM may be prepared again afterwards, but callers should treat a closed
2912 	 * instance as end-of-use unless they intentionally reinitialize it.
2913 	 * </p>
2914 	 */
2915 	@Override
2916 	public void close() throws IOException {
2917 		jrt.jrtCloseAll();
2918 		closeResolvedInputSource();
2919 		resolvedInputSource = null;
2920 		inputSourceFilelistAssignmentsApplied = false;
2921 	}
2922 
2923 	/**
2924 	 * Close the resolved {@link InputSource} if it implements {@link Closeable}.
2925 	 * This is used by {@link #close()} and by explicit rebind operations such as
2926 	 * {@link #prepareForEval(InputSource)} when the AVM switches to a different
2927 	 * source instance.
2928 	 */
2929 	private void closeResolvedInputSource() {
2930 		closeInputSource(resolvedInputSource);
2931 	}
2932 
2933 	private void closeInputSource(InputSource inputSource) {
2934 		if (!(inputSource instanceof Closeable)) {
2935 			return;
2936 		}
2937 		try {
2938 			((Closeable) inputSource).close();
2939 		} catch (IOException ignored) {
2940 			// Best-effort close.
2941 		}
2942 	}
2943 
2944 	private Object[] popArguments(long numArgs) {
2945 		Object[] args = new Object[(int) numArgs];
2946 		for (int i = (int) numArgs - 1; i >= 0; i--) {
2947 			args[i] = pop();
2948 		}
2949 		return args;
2950 	}
2951 
2952 	/**
2953 	 * sprintf() functionality
2954 	 */
2955 	private String sprintfFunction(long numArgs) {
2956 		Object[] argArray = popArguments(numArgs - 1);
2957 		String fmt = jrt.toAwkString(pop());
2958 		return jrt.getAwkSink().sprintf(fmt, argArray);
2959 	}
2960 
2961 	private void setNumOnJRT(long fieldNum, double num) {
2962 		String numString = jrt.toAwkString(Double.valueOf(num));
2963 
2964 		// same code as ASSIGN_AS_INPUT_FIELD
2965 		if (fieldNum == 0) {
2966 			jrt.setInputLine(numString);
2967 			jrt.jrtParseFields();
2968 		} else {
2969 			jrt.jrtSetInputField(numString, fieldNum);
2970 		}
2971 	}
2972 
2973 	private String execSubOrGSub(boolean isGsub) {
2974 		String newString;
2975 
2976 		// stack[0] = original field value
2977 		// stack[1] = replacement string
2978 		// stack[2] = ere
2979 		String orig = jrt.toAwkString(pop());
2980 		String repl = jrt.toAwkString(pop());
2981 		String ere = jrt.toAwkString(pop());
2982 		if (isGsub) {
2983 			newString = replaceAll(orig, ere, repl);
2984 		} else {
2985 			newString = replaceFirst(orig, ere, repl);
2986 		}
2987 
2988 		return newString;
2989 	}
2990 
2991 	private StringBuffer replaceFirstSb = new StringBuffer();
2992 
2993 	/**
2994 	 * sub() functionality
2995 	 */
2996 	private String replaceFirst(String orig, String ere, String repl) {
2997 		push(RegexRuntimeSupport.replaceFirst(orig, repl, ere, replaceFirstSb));
2998 		return replaceFirstSb.toString();
2999 	}
3000 
3001 	private StringBuffer replaceAllSb = new StringBuffer();
3002 
3003 	/**
3004 	 * gsub() functionality
3005 	 */
3006 	private String replaceAll(String orig, String ere, String repl) {
3007 		push(RegexRuntimeSupport.replaceAll(orig, repl, ere, replaceAllSb));
3008 		return replaceAllSb.toString();
3009 	}
3010 
3011 	/**
3012 	 * Awk variable assignment functionality.
3013 	 */
3014 	private void assign(long l, Object value, boolean isGlobal, PositionTracker position, boolean push) {
3015 		// check if curr value already refers to an array
3016 		if (runtimeStack.getVariable(l, isGlobal) instanceof Map) {
3017 			throw new AwkRuntimeException(position.lineNumber(), "cannot assign anything to an unindexed associative array");
3018 		}
3019 		if (push) {
3020 			push(value);
3021 		}
3022 		runtimeStack.setVariable(l, value, isGlobal);
3023 		// When specials are compiled correctly, they use ASSIGN_* and skip this path.
3024 	}
3025 
3026 	/**
3027 	 * Awk array element assignment functionality.
3028 	 */
3029 	private void assignArray(long offset, Object arrIdx, Object rhs, boolean isGlobal) {
3030 		assignMapElement(ensureMapVariable(offset, isGlobal), arrIdx, rhs);
3031 	}
3032 
3033 	private void assignMapElement(Map<Object, Object> array, Object arrIdx, Object rhs) {
3034 		checkScalar(arrIdx);
3035 		array.put(arrIdx, rhs);
3036 		push(rhs);
3037 	}
3038 
3039 	/**
3040 	 * Numerically increases an Awk variable by one; the result
3041 	 * is placed back into that variable.
3042 	 */
3043 	private Object inc(long l, boolean isGlobal) {
3044 		Object o = runtimeStack.getVariable(l, isGlobal);
3045 		if (o == null || o instanceof UninitializedObject) {
3046 			o = ZERO;
3047 			runtimeStack.setVariable(l, o, isGlobal);
3048 		}
3049 		Object updated = JRT.inc(o);
3050 		runtimeStack.setVariable(l, updated, isGlobal);
3051 		return o;
3052 	}
3053 
3054 	/**
3055 	 * Numerically decreases an Awk variable by one; the result
3056 	 * is placed back into that variable.
3057 	 */
3058 	private Object dec(long l, boolean isGlobal) {
3059 		Object o = runtimeStack.getVariable(l, isGlobal);
3060 		if (o == null || o instanceof UninitializedObject) {
3061 			o = ZERO;
3062 			runtimeStack.setVariable(l, o, isGlobal);
3063 		}
3064 		Object updated = JRT.dec(o);
3065 		runtimeStack.setVariable(l, updated, isGlobal);
3066 		return o;
3067 	}
3068 
3069 	/** {@inheritDoc} */
3070 	@Override
3071 	public final Object getRS() {
3072 		return jrt.getRSVar();
3073 	}
3074 
3075 	/** {@inheritDoc} */
3076 	@Override
3077 	public final Object getOFS() {
3078 		return jrt.getOFSVar();
3079 	}
3080 
3081 	public final Object getORS() {
3082 		return jrt.getORSVar();
3083 	}
3084 
3085 	/** {@inheritDoc} */
3086 	@Override
3087 	public final Object getSUBSEP() {
3088 		return jrt.getSUBSEPVar();
3089 	}
3090 
3091 	/**
3092 	 * Performs the global variable assignment within the runtime environment.
3093 	 * These assignments come from the ARGV list (bounded by ARGC), which, in
3094 	 * turn, come from the command-line arguments passed into Awk.
3095 	 *
3096 	 * @param nameValue The variable assignment in <i>name=value</i> form.
3097 	 */
3098 	@SuppressWarnings("unused")
3099 	private void setFilelistVariable(String nameValue) {
3100 		NameValueAssignment assignment = parseNameValueAssignment(nameValue);
3101 		String name = assignment.name;
3102 		Object obj = assignment.value;
3103 
3104 		// make sure we're not receiving funcname=value assignments
3105 		if (functionNames.contains(name)) {
3106 			throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + name + ").");
3107 		}
3108 
3109 		Integer offsetObj = globalVariableOffsets.get(name);
3110 		Boolean arrayObj = globalVariableArrays.get(name);
3111 
3112 		if (offsetObj != null) {
3113 			if (arrayObj.booleanValue()) {
3114 				throw new IllegalArgumentException("Cannot assign a scalar to a non-scalar variable (" + name + ").");
3115 			} else {
3116 				runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
3117 			}
3118 		} else if (runtimeStack.hasGlobalVariable(name)) {
3119 			runtimeStack.setGlobalVariable(name, obj);
3120 		}
3121 	}
3122 
3123 	/** {@inheritDoc} */
3124 	@Override
3125 	public final void assignVariable(String name, Object obj) {
3126 		// When offsets are not available yet, treat the assignment as part of this
3127 		// AVM's baseline initial-variable snapshot.
3128 		if (globalVariableOffsets == null || globalVariableArrays == null) {
3129 			Object normalized = normalizeExternalVariableValue(obj);
3130 			baseInitialVariables.put(name, normalized);
3131 			if (JRT.isJrtManagedSpecialVariable(name)) {
3132 				baseSpecialVariables.put(name, normalized);
3133 			}
3134 			return;
3135 		}
3136 
3137 		// make sure we're not receiving funcname=value assignments
3138 		if (functionNames.contains(name)) {
3139 			throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + name + ").");
3140 		}
3141 
3142 		Integer offsetObj = globalVariableOffsets.get(name);
3143 		Boolean arrayObj = globalVariableArrays.get(name);
3144 
3145 		if (offsetObj != null) {
3146 			Object normalized = normalizeExternalVariableValue(obj);
3147 			if (arrayObj.booleanValue()) {
3148 				if (normalized instanceof Map) {
3149 					runtimeStack.setFilelistVariable(offsetObj.intValue(), normalized);
3150 				} else {
3151 					throw new IllegalArgumentException(
3152 							"Cannot assign a scalar to a non-scalar variable (" + name + ").");
3153 				}
3154 			} else {
3155 				runtimeStack.setFilelistVariable(offsetObj.intValue(), normalized);
3156 			}
3157 		} else if (runtimeStack.hasGlobalVariable(name)) {
3158 			Object normalized = normalizeExternalVariableValue(obj);
3159 			runtimeStack.setGlobalVariable(name, normalized);
3160 		}
3161 	}
3162 
3163 	private void applyInputSourceFilelistAssignmentsIfNeeded() {
3164 		if (inputSourceFilelistAssignmentsApplied || resolvedInputSource instanceof StreamInputSource) {
3165 			return;
3166 		}
3167 		for (String argument : arguments) {
3168 			if (argument.indexOf('=') > 0) {
3169 				setFilelistVariable(argument);
3170 			}
3171 		}
3172 		inputSourceFilelistAssignmentsApplied = true;
3173 	}
3174 
3175 	/** {@inheritDoc} */
3176 	@Override
3177 	public Object getFS() {
3178 		return jrt.getFSVar();
3179 	}
3180 
3181 	/** {@inheritDoc} */
3182 	@Override
3183 	public Object getCONVFMT() {
3184 		return jrt.getCONVFMTString();
3185 	}
3186 
3187 	/** {@inheritDoc} */
3188 	@Override
3189 	public void resetFNR() {
3190 		jrt.setFNR(0);
3191 	}
3192 
3193 	/** {@inheritDoc} */
3194 	@Override
3195 	public void incFNR() {
3196 		long v = jrt.getFNR();
3197 		jrt.setFNR(v + 1);
3198 	}
3199 
3200 	/** {@inheritDoc} */
3201 	@Override
3202 	public void incNR() {
3203 		long v = jrt.getNR();
3204 		jrt.setNR(v + 1);
3205 	}
3206 
3207 	/** {@inheritDoc} */
3208 	@Override
3209 	public void setNF(Integer newNf) {
3210 		jrt.setNF(newNf);
3211 	}
3212 
3213 	/** {@inheritDoc} */
3214 	@Override
3215 	public void setFILENAME(String filename) {
3216 		jrt.setFILENAMEViaJrt(jrt.toInputScalar(filename));
3217 	}
3218 
3219 	/** {@inheritDoc} */
3220 	@Override
3221 	public Object getARGV() {
3222 		if (argvOffset == NULL_OFFSET) {
3223 			Map<Object, Object> argv = newAwkArray();
3224 			argv.put(0L, "jawk");
3225 			for (int i = 0; i < arguments.size(); i++) {
3226 				argv.put(Long.valueOf(i + 1L), jrt.toInputScalar(arguments.get(i)));
3227 			}
3228 			return argv;
3229 		}
3230 		return runtimeStack.getVariable(argvOffset, true);
3231 	}
3232 
3233 	/** {@inheritDoc} */
3234 	@Override
3235 	public Object getARGC() {
3236 		if (argcOffset == NULL_OFFSET) {
3237 			return Long.valueOf(arguments.size() + 1);
3238 		}
3239 		return runtimeStack.getVariable(argcOffset, true);
3240 	}
3241 
3242 	private String getOFMT() {
3243 		return jrt.getOFMTString();
3244 	}
3245 
3246 	private Map<Object, Object> newAwkArray() {
3247 		return JRT.createAwkMap(sortedArrayKeys);
3248 	}
3249 
3250 	private Map<Object, Object> ensureMapVariable(long offset, boolean isGlobal) {
3251 		Object value = runtimeStack.getVariable(offset, isGlobal);
3252 		if (value == null || value.equals(BLANK) || value instanceof UninitializedObject) {
3253 			Map<Object, Object> map = newAwkArray();
3254 			runtimeStack.setVariable(offset, map, isGlobal);
3255 			return map;
3256 		}
3257 		return toMap(value);
3258 	}
3259 
3260 	private Map<Object, Object> getMapVariable(long offset, boolean isGlobal) {
3261 		Object value = runtimeStack.getVariable(offset, isGlobal);
3262 		if (value == null || value.equals(BLANK) || value instanceof UninitializedObject) {
3263 			return null;
3264 		}
3265 		return toMap(value);
3266 	}
3267 
3268 	/**
3269 	 * Casts an AWK value to an associative array.
3270 	 *
3271 	 * @param value value to validate
3272 	 * @return the associative array value
3273 	 * @throws AwkRuntimeException when {@code value} is scalar
3274 	 */
3275 	private Map<Object, Object> toMap(Object value) {
3276 		if (!(value instanceof Map)) {
3277 			throw new AwkRuntimeException("Attempting to treat a scalar as an array.");
3278 		}
3279 		@SuppressWarnings("unchecked")
3280 		Map<Object, Object> map = (Map<Object, Object>) value;
3281 		return map;
3282 	}
3283 
3284 	/**
3285 	 * Ensures a value is scalar before using it in a scalar-only context such as
3286 	 * a subscript component.
3287 	 *
3288 	 * @param value value to validate
3289 	 * @throws AwkRuntimeException when {@code value} is an array
3290 	 */
3291 	private void checkScalar(Object value) {
3292 		if (value instanceof Map) {
3293 			throw new AwkRuntimeException("Attempting to use an array in a scalar context.");
3294 		}
3295 	}
3296 
3297 	/**
3298 	 * Returns the nested associative array stored in {@code map[key]}, creating it
3299 	 * when the key is undefined.
3300 	 *
3301 	 * @param map containing array
3302 	 * @param key nested-array key
3303 	 * @return the nested associative array stored at {@code key}
3304 	 * @throws AwkRuntimeException when {@code key} is scalar-incompatible or when
3305 	 *         the existing slot contains a scalar
3306 	 */
3307 	private Map<Object, Object> ensureArrayInArray(Map<Object, Object> map, Object key) {
3308 		checkScalar(key);
3309 		Object value = JRT.getAwkValue(map, key);
3310 		if (value == null || value.equals(BLANK) || value instanceof UninitializedObject) {
3311 			Map<Object, Object> nested = newAwkArray();
3312 			map.put(key, nested);
3313 			return nested;
3314 		}
3315 		if (!(value instanceof Map)) {
3316 			throw new AwkRuntimeException("Attempting to use a scalar as an array.");
3317 		}
3318 		@SuppressWarnings("unchecked")
3319 		Map<Object, Object> nested = (Map<Object, Object>) value;
3320 		return nested;
3321 	}
3322 
3323 	private Object normalizeExternalVariableValue(Object value) {
3324 		if (value instanceof String) {
3325 			return jrt.toInputScalar(value);
3326 		}
3327 		if (!(value instanceof Map) && !(value instanceof List)) {
3328 			return value;
3329 		}
3330 		return AssocArray.normalizeValue(value, sortedArrayKeys);
3331 	}
3332 
3333 	private static final UninitializedObject BLANK = new UninitializedObject();
3334 
3335 	/**
3336 	 * Global names that must not participate in persistent memory even though they
3337 	 * are technically user-visible variables.
3338 	 */
3339 	private static final Set<String> NON_PERSISTENT_GLOBALS = new HashSet<>(
3340 			Arrays.asList("ARGV", "ARGC", "ENVIRON", "RSTART", "RLENGTH", "IGNORECASE"));
3341 
3342 	private static final class NameValueAssignment {
3343 		private final String name;
3344 		private final Object value;
3345 
3346 		private NameValueAssignment(String name, Object value) {
3347 			this.name = name;
3348 			this.value = value;
3349 		}
3350 	}
3351 
3352 	private static final class SingleRecordInputSource implements InputSource {
3353 
3354 		private final String record;
3355 		private boolean consumed;
3356 
3357 		private SingleRecordInputSource(String record) {
3358 			this.record = record;
3359 		}
3360 
3361 		@Override
3362 		public boolean nextRecord() {
3363 			if (consumed || record == null) {
3364 				return false;
3365 			}
3366 			consumed = true;
3367 			return true;
3368 		}
3369 
3370 		@Override
3371 		public String getRecordText() {
3372 			return consumed ? record : null;
3373 		}
3374 
3375 		@Override
3376 		public List<String> getFields() {
3377 			return null;
3378 		}
3379 
3380 		@Override
3381 		public boolean isFromFilenameList() {
3382 			return false;
3383 		}
3384 	}
3385 
3386 	/**
3387 	 * The value of an address which is not yet assigned a tuple index.
3388 	 */
3389 	public static final int NULL_OFFSET = -1;
3390 
3391 }