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.AbstractSet;
29  import java.util.Iterator;
30  import java.util.Arrays;
31  import java.util.HashSet;
32  import java.util.LinkedHashMap;
33  import java.util.LinkedHashSet;
34  import java.util.Objects;
35  import java.util.ArrayDeque;
36  import java.util.ArrayList;
37  import java.util.Collections;
38  import java.util.Deque;
39  import java.util.Enumeration;
40  import java.util.HashMap;
41  import java.util.IdentityHashMap;
42  import java.util.List;
43  import java.util.Locale;
44  import java.util.Map;
45  import java.util.Set;
46  import java.util.function.BiConsumer;
47  import java.util.regex.Pattern;
48  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
49  import io.jawk.AwkExpression;
50  import io.jawk.AwkProgram;
51  import io.jawk.AwkSandboxException;
52  import io.jawk.ExitException;
53  import io.jawk.ext.AbstractExtension;
54  import io.jawk.ext.ExtensionFunction;
55  import io.jawk.ext.ForInKeyOrder;
56  import io.jawk.ext.JawkExtension;
57  import io.jawk.intermediate.Address;
58  import io.jawk.intermediate.BuiltinFunction;
59  import io.jawk.intermediate.Opcode;
60  import io.jawk.intermediate.PositionTracker;
61  import io.jawk.intermediate.Tuple;
62  import io.jawk.intermediate.Tuple.BooleanTuple;
63  import io.jawk.intermediate.Tuple.CallFunctionTuple;
64  import io.jawk.intermediate.Tuple.ClassTuple;
65  import io.jawk.intermediate.Tuple.CountAndAppendTuple;
66  import io.jawk.intermediate.Tuple.CountTuple;
67  import io.jawk.intermediate.Tuple.DereferenceTuple;
68  import io.jawk.intermediate.Tuple.ExtensionTuple;
69  import io.jawk.intermediate.Tuple.IndirectCallTuple;
70  import io.jawk.intermediate.Tuple.IndirectFunctionTarget;
71  import io.jawk.intermediate.Tuple.InputFieldTuple;
72  import io.jawk.intermediate.Tuple.LongTuple;
73  import io.jawk.intermediate.Tuple.PushDoubleTuple;
74  import io.jawk.intermediate.Tuple.PushLongTuple;
75  import io.jawk.intermediate.Tuple.PushStringTuple;
76  import io.jawk.intermediate.Tuple.RegexTuple;
77  import io.jawk.intermediate.Tuple.ScalarPopTuple;
78  import io.jawk.intermediate.Tuple.SubstitutionVariableTuple;
79  import io.jawk.intermediate.Tuple.VariableTuple;
80  import io.jawk.intermediate.UninitializedObject;
81  import io.jawk.intermediate.UntypedObject;
82  import io.jawk.jrt.AssocArray;
83  import io.jawk.jrt.AwkRuntimeException;
84  import io.jawk.jrt.AwkSink;
85  import io.jawk.jrt.BlockManager;
86  import io.jawk.jrt.BlockObject;
87  import io.jawk.jrt.ConditionPair;
88  import io.jawk.jrt.InputSource;
89  import io.jawk.jrt.JRT;
90  import io.jawk.jrt.VariableManager;
91  import io.jawk.util.AwkSettings;
92  import io.jawk.jrt.BSDRandom;
93  
94  /**
95   * The Jawk interpreter.
96   * <p>
97   * It takes tuples constructed by the intermediate step
98   * and executes each tuple in accordance to their instruction semantics.
99   * The tuples correspond to the Awk script compiled by the parser.
100  * The interpreter consists of an instruction processor (interpreter),
101  * a runtime stack, and machinery to support the instruction set
102  * contained within the tuples.
103  * <p>
104  * The interpreter runs completely independent of the frontend/intermediate step.
105  * In fact, an intermediate file produced by Jawk is sufficient to
106  * execute on this interpreter. The binding data-structure is
107  * the {@link AwkSettings}, which can contain options pertinent to
108  * the interpreter. For example, the interpreter must know about
109  * the -v command line argument values, as well as the file/variable list
110  * parameter values (ARGC/ARGV) after the script on the command line.
111  * However, if programmatic access to the AVM is required, meaningful
112  * {@link AwkSettings} are not required.
113  * <p>
114  * Semantic analysis has occurred prior to execution of the interpreter.
115  * Therefore, the interpreter throws AwkRuntimeExceptions upon most
116  * errors/conditions. It can also throw a <code>java.lang.Error</code> if an
117  * interpreter error is encountered.
118  * <p>
119  * AVM instances are reusable, but they are not thread-safe. Reuse the same
120  * interpreter only sequentially, or create one AVM per concurrent execution.
121  * When AVM is used directly, callers own its lifecycle and must
122  * {@link #close()} it when done.
123  *
124  * @author Danny Daglas
125  */
126 public class AVM implements VariableManager, Closeable {
127 
128 	private RuntimeStack runtimeStack = new RuntimeStack();
129 
130 	// operand stack
131 	private Deque<Object> operandStack = new ArrayDeque<Object>();
132 	// Untyped array elements currently passed as user-function arguments, in
133 	// call order. Each reference lives exactly as long as the call frame that
134 	// received it, so this stack stays empty unless an untyped element
135 	// argument is in flight.
136 	private final Deque<IndirectArrayArgumentReference> elementArgumentReferences = new ArrayDeque<IndirectArrayArgumentReference>();
137 	private List<String> arguments;
138 	private boolean sortedArrayKeys;
139 	private final Map<String, Object> baseInitialVariables;
140 	private final Map<String, Object> baseSpecialVariables;
141 	private Map<String, Object> executionInitialVariables;
142 	private Map<String, Object> executionSpecialVariables;
143 	private JRT jrt;
144 	private Map<String, JawkExtension> extensionInstances;
145 
146 	private static final Object NULL_OPERAND = new Object();
147 
148 	// stack methods
149 	// private Object pop() { return operandStack.removeFirst(); }
150 	// private void push(Object o) { operandStack.addLast(o); }
151 	private Object pop() {
152 		Object value = operandStack.pop();
153 		return value == NULL_OPERAND ? null : value;
154 	}
155 
156 	private void push(Object o) {
157 		operandStack.push(o == null ? NULL_OPERAND : o);
158 	}
159 
160 	private final AwkSettings settings;
161 	private final boolean profiling;
162 	private final Map<Opcode, ProfilingReport.Accumulator> tupleProfilingStats;
163 	private final Map<String, ProfilingReport.Accumulator> functionProfilingStats;
164 	private final Deque<ActiveFunction> activeProfilingFunctions;
165 	private InputSource resolvedInputSource;
166 	private AwkExpression installedEvalExpression;
167 	private boolean mergedGlobalLayoutActive;
168 
169 	/** Optional extension-provided ordering for {@code for (index in array)} traversal. */
170 	private ForInKeyOrder forInKeyOrder;
171 
172 	/** Description of the primary script source, for runtime diagnostics. */
173 	private String sourceDescription;
174 
175 	/** Script line of the extension call currently being dispatched. */
176 	private int currentLineNumber;
177 
178 	/** Whether the extension beforeStart hooks already ran for this AVM. */
179 	private boolean beforeStartHooksExecuted;
180 
181 	/** Offset of a materialized SYMTAB array, for live operand-assignment updates. */
182 	private long symtabOffset = NULL_OFFSET;
183 
184 	/**
185 	 * Construct the interpreter.
186 	 * <p>
187 	 * Provided to allow programmatic construction of the interpreter
188 	 * outside of the framework which is used by Jawk.
189 	 */
190 	public AVM() {
191 		this(null, Collections.<String, JawkExtension>emptyMap());
192 	}
193 
194 	/**
195 	 * Construct the interpreter, accepting parameters which may have been
196 	 * set on the command-line arguments to the JVM.
197 	 *
198 	 * @param parameters The parameters affecting the behavior of the
199 	 *        interpreter.
200 	 * @param extensionInstances Map of the extensions to load
201 	 */
202 	public AVM(final AwkSettings parameters,
203 			final Map<String, JawkExtension> extensionInstances) {
204 		this(parameters, extensionInstances, false);
205 	}
206 
207 	/**
208 	 * Construct the interpreter, optionally enabling runtime profiling.
209 	 *
210 	 * @param parameters The parameters affecting the behavior of the
211 	 *        interpreter.
212 	 * @param extensionInstances Map of the extensions to load
213 	 * @param profilingEnabled Whether to collect profiling statistics
214 	 */
215 	public AVM(
216 			final AwkSettings parameters,
217 			final Map<String, JawkExtension> extensionInstances,
218 			final boolean profilingEnabled) {
219 		this.settings = parameters != null ? parameters : AwkSettings.DEFAULT_SETTINGS;
220 		this.extensionInstances = extensionInstances == null ?
221 				Collections.<String, JawkExtension>emptyMap() : extensionInstances;
222 		this.profiling = profilingEnabled;
223 		if (profilingEnabled) {
224 			this.tupleProfilingStats = new java.util.EnumMap<Opcode, ProfilingReport.Accumulator>(Opcode.class);
225 			this.functionProfilingStats = new LinkedHashMap<String, ProfilingReport.Accumulator>();
226 			this.activeProfilingFunctions = new ArrayDeque<ActiveFunction>();
227 		} else {
228 			this.tupleProfilingStats = null;
229 			this.functionProfilingStats = null;
230 			this.activeProfilingFunctions = null;
231 		}
232 
233 		arguments = Collections.emptyList();
234 		sortedArrayKeys = this.settings.isUseSortedArrayKeys();
235 		baseInitialVariables = new HashMap<String, Object>(this.settings.getVariables());
236 		baseSpecialVariables = JRT.copySpecialVariables(baseInitialVariables);
237 		executionInitialVariables = baseInitialVariables;
238 		executionSpecialVariables = baseSpecialVariables;
239 
240 		jrt = createJrt();
241 		initExtensions();
242 	}
243 
244 	protected JRT createJrt() {
245 		return new JRT(this, this.settings.getLocale(), AwkSink.NOP_SINK, null);
246 	}
247 
248 	/**
249 	 * Returns the runtime settings associated with this interpreter.
250 	 *
251 	 * @return the settings, never {@code null}
252 	 */
253 	protected AwkSettings getSettings() {
254 		return settings;
255 	}
256 
257 	/**
258 	 * Returns the JRT (Jawk Runtime) instance associated with this interpreter.
259 	 *
260 	 * @return the JRT instance, never {@code null}
261 	 */
262 	@SuppressFBWarnings("EI_EXPOSE_REP")
263 	public JRT getJrt() {
264 		return jrt;
265 	}
266 
267 	/**
268 	 * Sets the sink used by default {@code print} and {@code printf}
269 	 * operations on this runtime.
270 	 *
271 	 * @param sink sink to use
272 	 */
273 	public void setAwkSink(AwkSink sink) {
274 		jrt.setAwkSink(Objects.requireNonNull(sink, "sink"));
275 	}
276 
277 	/**
278 	 * Sets the stream used for the stderr output of spawned processes
279 	 * (e.g.&nbsp;{@code system("...")}).
280 	 *
281 	 * @param errorStream stream to receive process stderr
282 	 */
283 	public void setErrorStream(PrintStream errorStream) {
284 		jrt.setErrorStream(errorStream);
285 	}
286 
287 	/**
288 	 * Sets the stream that receives runtime warning messages (gawk-style
289 	 * diagnostics). Warnings default to {@link System#err}.
290 	 *
291 	 * @param warningStream stream to receive runtime warnings
292 	 */
293 	public void setWarningStream(PrintStream warningStream) {
294 		jrt.setWarningStream(warningStream);
295 	}
296 
297 	/**
298 	 * Registers the hook that decides the key traversal order of
299 	 * {@code for (index in array)} statements.
300 	 * <p>
301 	 * When no hook is registered, iteration uses the array's natural key order.
302 	 * Extensions typically register a hook from their {@code beforeStart}
303 	 * method; the last registration wins.
304 	 * </p>
305 	 *
306 	 * @param keyOrder traversal-order provider, or {@code null} to restore the
307 	 *        natural key order
308 	 */
309 	public void setForInKeyOrder(ForInKeyOrder keyOrder) {
310 		forInKeyOrder = keyOrder;
311 	}
312 
313 	/**
314 	 * Returns the default sink used by this runtime.
315 	 *
316 	 * @return the current AWK sink
317 	 */
318 	public AwkSink getAwkSink() {
319 		return jrt.getAwkSink();
320 	}
321 
322 	/**
323 	 * Returns the locale configured for this runtime.
324 	 *
325 	 * @return runtime locale
326 	 */
327 	protected Locale getLocale() {
328 		return jrt.getLocale();
329 	}
330 
331 	/**
332 	 * Evaluates a compiled expression against the AVM state exactly as it
333 	 * currently stands.
334 	 *
335 	 * @param expression compiled expression to evaluate
336 	 * @return the resulting value
337 	 * @throws IOException if evaluation fails
338 	 */
339 	public Object eval(AwkExpression expression) throws IOException {
340 		AwkExpression compiledExpression = Objects.requireNonNull(expression, "expression");
341 		installExpressionMetadata(compiledExpression);
342 
343 		try {
344 			executeTuples(compiledExpression.top());
345 		} catch (ExitException e) {
346 			// Expression tuples must never contain EXIT opcodes. If callers pass an
347 			// invalid compiled expression, fail fast without poisoning later evals.
348 			throwExitException = false;
349 			exitCode = 0;
350 			throw new IllegalStateException("eval(AwkExpression) cannot execute EXIT opcodes.", e);
351 		}
352 		return operandStack.isEmpty() ? null : JRT.toJavaScalar(pop());
353 	}
354 
355 	/**
356 	 * Evaluates a compiled expression against the supplied input source.
357 	 *
358 	 * @param expression compiled expression to evaluate
359 	 * @param inputSource input source providing the current record
360 	 * @return the resulting value
361 	 * @throws IOException if evaluation fails
362 	 */
363 	public Object eval(AwkExpression expression, InputSource inputSource) throws IOException {
364 		return eval(expression, inputSource, null);
365 	}
366 
367 	/**
368 	 * Evaluates a compiled expression against the supplied input source with
369 	 * per-call variable overrides.
370 	 *
371 	 * @param expression compiled expression to evaluate
372 	 * @param inputSource input source providing the current record
373 	 * @param variableOverrides additional variable assignments applied on top of
374 	 *        the settings-level variables (may be {@code null})
375 	 * @return the resulting value
376 	 * @throws IOException if evaluation fails
377 	 */
378 	public Object eval(
379 			AwkExpression expression,
380 			InputSource inputSource,
381 			Map<String, Object> variableOverrides)
382 			throws IOException {
383 		prepareForEval(inputSource, Collections.<String>emptyList(), variableOverrides);
384 		return eval(expression);
385 	}
386 
387 	/**
388 	 * Executes a compiled AWK program with the current runtime defaults.
389 	 *
390 	 * @param program compiled program to execute
391 	 * @param inputSource input source providing records
392 	 * @throws ExitException when the program terminates via {@code exit}
393 	 * @throws IOException if execution fails
394 	 */
395 	public void execute(AwkProgram program, InputSource inputSource) throws ExitException, IOException {
396 		execute(program, inputSource, Collections.<String>emptyList(), null);
397 	}
398 
399 	/**
400 	 * Executes a compiled AWK program with explicit runtime arguments.
401 	 *
402 	 * @param program compiled program to execute
403 	 * @param inputSource input source providing records
404 	 * @param runtimeArguments name=value or filename entries from the command line
405 	 * @throws ExitException when the program terminates via {@code exit}
406 	 * @throws IOException if execution fails
407 	 */
408 	public void execute(AwkProgram program, InputSource inputSource, List<String> runtimeArguments)
409 			throws ExitException,
410 			IOException {
411 		execute(program, inputSource, runtimeArguments, null);
412 	}
413 
414 	/**
415 	 * Executes a compiled AWK program with explicit runtime arguments and
416 	 * variable overrides.
417 	 *
418 	 * @param program compiled program to execute
419 	 * @param inputSource input source providing records
420 	 * @param runtimeArguments name=value or filename entries from the command line
421 	 * @param variableOverrides additional variable assignments applied on top of
422 	 *        the settings-level variables (may be {@code null})
423 	 * @throws ExitException when the program terminates via {@code exit}
424 	 * @throws IOException if execution fails
425 	 */
426 	public void execute(
427 			AwkProgram program,
428 			InputSource inputSource,
429 			List<String> runtimeArguments,
430 			Map<String, Object> variableOverrides)
431 			throws ExitException,
432 			IOException {
433 		AwkProgram compiledProgram = Objects.requireNonNull(program, "program");
434 		InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
435 		resetRuntimeState(runtimeArguments, variableOverrides);
436 		installProgramMetadata(compiledProgram);
437 
438 		jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
439 		if (!executionSpecialVariables.isEmpty()) {
440 			jrt.applySpecialVariables(executionSpecialVariables);
441 		}
442 		rebindResolvedInputSource(resolvedSource);
443 		executeTuples(compiledProgram.top());
444 	}
445 
446 	/**
447 	 * Executes a compiled AWK program while persisting user-defined global
448 	 * variables across repeated executions on this AVM instance.
449 	 * <p>
450 	 * Before the new program starts, this method imports any user-defined
451 	 * globals currently materialized in the AVM and remaps them onto the
452 	 * incoming program's compiled global slots.
453 	 *
454 	 * @param program compiled program to execute
455 	 * @param inputSource input source providing records
456 	 * @throws ExitException when the program terminates via {@code exit}
457 	 * @throws IOException if execution fails
458 	 */
459 	public void executePersistingGlobals(AwkProgram program, InputSource inputSource)
460 			throws ExitException,
461 			IOException {
462 		executePersistingGlobals(program, inputSource, Collections.<String>emptyList(), null);
463 	}
464 
465 	/**
466 	 * Executes a compiled AWK program while persisting user-defined global
467 	 * variables across repeated executions on this AVM instance.
468 	 * <p>
469 	 * Before the new program starts, this method imports any user-defined
470 	 * globals currently materialized in the AVM and remaps them onto the
471 	 * incoming program's compiled global slots.
472 	 *
473 	 * @param program compiled program to execute
474 	 * @param inputSource input source providing records
475 	 * @param runtimeArguments name=value or filename entries from the command line
476 	 * @throws ExitException when the program terminates via {@code exit}
477 	 * @throws IOException if execution fails
478 	 */
479 	public void executePersistingGlobals(
480 			AwkProgram program,
481 			InputSource inputSource,
482 			List<String> runtimeArguments)
483 			throws ExitException,
484 			IOException {
485 		executePersistingGlobals(program, inputSource, runtimeArguments, null);
486 	}
487 
488 	/**
489 	 * Executes a compiled AWK program while persisting user-defined global
490 	 * variables across repeated executions on this AVM instance.
491 	 * <p>
492 	 * Before the new program starts, this method imports any user-defined
493 	 * globals currently materialized in the AVM and remaps them onto the
494 	 * incoming program's compiled global slots.
495 	 *
496 	 * @param program compiled program to execute
497 	 * @param inputSource input source providing records
498 	 * @param runtimeArguments name=value or filename entries from the command line
499 	 * @param variableOverrides additional variable assignments applied on top of
500 	 *        the settings-level variables (may be {@code null})
501 	 * @throws ExitException when the program terminates via {@code exit}
502 	 * @throws IOException if execution fails
503 	 */
504 	public void executePersistingGlobals(
505 			AwkProgram program,
506 			InputSource inputSource,
507 			List<String> runtimeArguments,
508 			Map<String, Object> variableOverrides)
509 			throws ExitException,
510 			IOException {
511 		AwkProgram compiledProgram = Objects.requireNonNull(program, "program");
512 		InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
513 		mergeRuntimeState(runtimeArguments, variableOverrides, compiledProgram);
514 
515 		jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
516 		if (!executionSpecialVariables.isEmpty()) {
517 			jrt.applySpecialVariables(executionSpecialVariables);
518 		}
519 		rebindResolvedInputSource(resolvedSource);
520 		executeTuples(compiledProgram.top());
521 	}
522 
523 	/**
524 	 * Clears the user-defined globals retained in the current runtime stack.
525 	 * <p>
526 	 * The next {@link #executePersistingGlobals(AwkProgram, InputSource, List, Map)}
527 	 * call will therefore start from an empty persistent global bank.
528 	 */
529 	public void clearPersistentGlobals() {
530 		runtimeStack.clearGlobals();
531 		mergedGlobalLayoutActive = false;
532 	}
533 
534 	/**
535 	 * Captures the user-defined globals currently retained by this AVM for
536 	 * persistent execution.
537 	 * <p>
538 	 * The returned snapshot is serializable and can later be fed back into
539 	 * {@link #restorePersistentMemory(Map)} on this or another AVM instance.
540 	 *
541 	 * @return serializable snapshot of the persistent user-global bank
542 	 */
543 	public Map<String, Object> snapshotPersistentMemory() {
544 		return new LinkedHashMap<>(collectPersistentGlobalValues());
545 	}
546 
547 	/**
548 	 * Restores the user-defined globals retained by this AVM from a previously
549 	 * captured persistent-memory snapshot.
550 	 * <p>
551 	 * Restoring a snapshot replaces the current retained global bank. The next
552 	 * {@link #executePersistingGlobals(AwkProgram, InputSource, List, Map)} call
553 	 * will merge these globals into the compiled layout of the incoming program.
554 	 *
555 	 * @param snapshot snapshot to restore
556 	 */
557 	public void restorePersistentMemory(Map<String, Object> snapshot) {
558 		Map<String, Object> restoredSnapshot = Objects.requireNonNull(snapshot, "snapshot");
559 		Map<String, Object> restoredGlobals = filterToPersistentEligible(restoredSnapshot);
560 		runtimeStack.clearGlobals();
561 		if (!restoredGlobals.isEmpty()) {
562 			runtimeStack.rebindGlobals(new ArrayList<>(restoredGlobals.keySet()));
563 			applyGlobalsToStack(restoredGlobals);
564 		}
565 		mergedGlobalLayoutActive = false;
566 	}
567 
568 	private void initExtensions() {
569 		if (extensionInstances.isEmpty()) {
570 			return;
571 		}
572 		Set<JawkExtension> initialized = new LinkedHashSet<JawkExtension>();
573 		for (JawkExtension extension : extensionInstances.values()) {
574 			if (initialized.add(extension)) {
575 				extension.init(this, jrt, settings); // this = VariableManager
576 			}
577 		}
578 	}
579 
580 	// Offsets for globals that remain runtime-managed by the tuple stream.
581 	// ARGC is always materialized; ENVIRON and ARGV are emitted on demand.
582 	private long environOffset = NULL_OFFSET;
583 	private long argcOffset = NULL_OFFSET;
584 	private long argvOffset = NULL_OFFSET;
585 
586 	private static final Integer ZERO = Integer.valueOf(0);
587 	private static final Integer ONE = Integer.valueOf(1);
588 
589 	/** Random number generator used for rand() */
590 	private final BSDRandom randomNumberGenerator = new BSDRandom(1);
591 
592 	/**
593 	 * Address of the END blocks section, read from the compiled program;
594 	 * {@code null} for expression streams.
595 	 */
596 	private Address exitAddress = null;
597 
598 	/**
599 	 * Address of the ENDFILE section, read from the compiled program when it
600 	 * has BEGINFILE/ENDFILE rules; {@code null} otherwise.
601 	 */
602 	private Address endFileAddress = null;
603 
604 	/**
605 	 * Address of the NEXT_FILE tuple that opens each input file, read from
606 	 * the compiled program when it requires per-file input stepping
607 	 * (BEGINFILE/ENDFILE rules or a {@code nextfile} statement);
608 	 * {@code null} otherwise.
609 	 */
610 	private Address nextFileAddress = null;
611 
612 	/**
613 	 * <code>true</code> if execution position is within a BEGINFILE rule;
614 	 * <code>false</code> otherwise.
615 	 */
616 	private boolean withinBeginFileBlocks = false;
617 
618 	/**
619 	 * <code>true</code> if execution position is within an ENDFILE rule;
620 	 * <code>false</code> otherwise.
621 	 */
622 	private boolean withinEndFileBlocks = false;
623 
624 	/**
625 	 * <code>true</code> once the per-file main input loop has advanced to its
626 	 * first input file; <code>false</code> while still in the BEGIN blocks.
627 	 */
628 	private boolean inputFileLoopStarted = false;
629 
630 	/**
631 	 * <code>true</code> if execution position is within an END block;
632 	 * <code>false</code> otherwise.
633 	 */
634 	private boolean withinEndBlocks = false;
635 
636 	/**
637 	 * Exit code set by the <code>exit NN</code> command (0 by default)
638 	 */
639 	private int exitCode = 0;
640 
641 	/**
642 	 * Whether <code>exit</code> has been called and we should throw ExitException
643 	 */
644 	private boolean throwExitException = false;
645 
646 	/**
647 	 * Maps global variable names to their global array offsets.
648 	 * It is useful when passing variable assignments from the file-list
649 	 * portion of the command-line arguments.
650 	 */
651 	private Map<String, Integer> globalVariableOffsets;
652 	/**
653 	 * Indicates whether the variable, by name, is a scalar
654 	 * or not. If not, then it is an Associative Array.
655 	 */
656 	private Map<String, Boolean> globalVariableArrays;
657 	private Set<String> functionNames = Collections.emptySet();
658 	private Map<String, Integer> initializedEvalGlobalVariableOffsets;
659 	private Map<String, Boolean> initializedEvalGlobalVariableArrays;
660 
661 	/**
662 	 * Resets the interpreter to a fresh eval state and binds one text record as
663 	 * the current input.
664 	 *
665 	 * @param input text record to expose as {@code $0}
666 	 * @return {@code true} when a record was prepared, {@code false} when the
667 	 *         provided text represents no input
668 	 * @throws IOException if binding the input fails
669 	 */
670 	public boolean prepareForEval(String input) throws IOException {
671 		return prepareForEval(new SingleRecordInputSource(input), Collections.<String>emptyList(), null);
672 	}
673 
674 	/**
675 	 * Resets the interpreter to a fresh eval state and binds at most one record
676 	 * from the provided input source as the current input. Calling this method
677 	 * again on the same source advances to the next available record.
678 	 *
679 	 * @param inputSource source providing the record to bind
680 	 * @return {@code true} when a record was prepared, {@code false} when the
681 	 *         source is exhausted
682 	 * @throws IOException if reading the input fails
683 	 */
684 	public boolean prepareForEval(InputSource inputSource) throws IOException {
685 		return prepareForEval(inputSource, Collections.<String>emptyList(), null);
686 	}
687 
688 	private boolean prepareForEval(
689 			InputSource inputSource,
690 			List<String> runtimeArguments,
691 			Map<String, Object> variableOverrides)
692 			throws IOException {
693 		InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
694 		resetRuntimeState(runtimeArguments, variableOverrides);
695 		rebindResolvedInputSource(resolvedSource);
696 
697 		jrt.jrtCloseAll();
698 		jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
699 		if (!executionSpecialVariables.isEmpty()) {
700 			jrt.applySpecialVariables(executionSpecialVariables);
701 		}
702 		return jrt.consumeInputForEval(resolvedInputSource);
703 	}
704 
705 	private void resetRuntimeState(List<String> runtimeArguments, Map<String, Object> variableOverrides) {
706 		resetTransientRuntimeState(runtimeArguments, variableOverrides);
707 		runtimeStack.clearGlobals();
708 	}
709 
710 	private void resetTransientRuntimeState(List<String> runtimeArguments, Map<String, Object> variableOverrides) {
711 		// Reset the AVM-owned state that must not leak across executions.
712 		operandStack.clear();
713 		elementArgumentReferences.clear();
714 		environOffset = NULL_OFFSET;
715 		argcOffset = NULL_OFFSET;
716 		argvOffset = NULL_OFFSET;
717 		symtabOffset = NULL_OFFSET;
718 		exitAddress = null;
719 		endFileAddress = null;
720 		nextFileAddress = null;
721 		withinBeginFileBlocks = false;
722 		withinEndFileBlocks = false;
723 		inputFileLoopStarted = false;
724 		withinEndBlocks = false;
725 		exitCode = 0;
726 		throwExitException = false;
727 		globalVariableOffsets = null;
728 		globalVariableArrays = null;
729 		functionNames = Collections.emptySet();
730 		initializedEvalGlobalVariableOffsets = null;
731 		initializedEvalGlobalVariableArrays = null;
732 		installedEvalExpression = null;
733 		mergedGlobalLayoutActive = false;
734 		runtimeStack.resetTransientState();
735 		randomNumberGenerator.setSeed(1);
736 
737 		prepareExecutionInputs(runtimeArguments, variableOverrides);
738 	}
739 
740 	private void installExpressionMetadata(AwkExpression compiledExpression) {
741 		if (installedEvalExpression == compiledExpression) {
742 			return;
743 		}
744 		globalVariableOffsets = compiledExpression.getGlobalVariableOffsetMap();
745 		globalVariableArrays = compiledExpression.getGlobalVariableAarrayMap();
746 		functionNames = compiledExpression.getFunctionNameSet();
747 		installedEvalExpression = compiledExpression;
748 	}
749 
750 	private void installProgramMetadata(AwkProgram compiledProgram) {
751 		globalVariableOffsets = compiledProgram.getGlobalVariableOffsetMap();
752 		globalVariableArrays = compiledProgram.getGlobalVariableAarrayMap();
753 		functionNames = compiledProgram.getFunctionNameSet();
754 		sourceDescription = compiledProgram.getSourceDescription();
755 		exitAddress = compiledProgram.getExitAddress();
756 		endFileAddress = compiledProgram.getEndFileAddress();
757 		nextFileAddress = compiledProgram.getNextFileAddress();
758 	}
759 
760 	private void rebindResolvedInputSource(InputSource resolvedSource) {
761 		InputSource previousResolvedSource = resolvedInputSource;
762 		if (previousResolvedSource != null && previousResolvedSource != resolvedSource) {
763 			closeInputSource(previousResolvedSource);
764 		}
765 		resolvedInputSource = resolvedSource;
766 	}
767 
768 	private boolean hasCompatibleEvalGlobalLayout(long numGlobals) {
769 		Object[] globals = runtimeStack.getNumGlobals();
770 		return globals != null
771 				&& globals.length == numGlobals
772 				&& Objects.equals(initializedEvalGlobalVariableOffsets, globalVariableOffsets)
773 				&& Objects.equals(initializedEvalGlobalVariableArrays, globalVariableArrays);
774 	}
775 
776 	/**
777 	 * Resets transient execution state, installs the new program metadata, and
778 	 * merges the previously retained user globals into the new compiled global
779 	 * layout.
780 	 *
781 	 * @param runtimeArguments name=value or filename entries for this execution
782 	 * @param variableOverrides per-call variable overrides for this execution
783 	 * @param compiledProgram program whose global layout should become active
784 	 */
785 	private void mergeRuntimeState(
786 			List<String> runtimeArguments,
787 			Map<String, Object> variableOverrides,
788 			AwkProgram compiledProgram) {
789 		Map<String, Object> carriedGlobals = collectPersistentGlobalValues();
790 		resetTransientRuntimeState(runtimeArguments, variableOverrides);
791 		installProgramMetadata(compiledProgram);
792 
793 		Map<String, Object> basePersistentSeeds = collectBasePersistentGlobalSeeds();
794 		Map<String, Object> executionUserSeeds = collectExecutionUserGlobalSeeds(variableOverrides);
795 		List<String> mergedGlobalNamesByOffset = buildMergedGlobalNamesByOffset(
796 				carriedGlobals,
797 				basePersistentSeeds,
798 				executionUserSeeds);
799 
800 		runtimeStack.rebindGlobals(mergedGlobalNamesByOffset);
801 		applyGlobalsToStack(carriedGlobals);
802 		applyGlobalsToStack(basePersistentSeeds);
803 		applyGlobalsToStack(executionUserSeeds);
804 		mergedGlobalLayoutActive = true;
805 	}
806 
807 	/**
808 	 * Returns whether the current runtime stack already contains a merged global
809 	 * layout for the compiled program about to execute.
810 	 * <p>
811 	 * Persistent execution may append previously retained globals after the
812 	 * compiled globals of the incoming program. The tuple stream only dereferences
813 	 * the prefix defined by {@code SET_NUM_GLOBALS}, so appended globals are valid
814 	 * as long as the compiled prefix still matches name-for-name and offset-for-offset.
815 	 *
816 	 * @param numGlobals number of globals compiled into the active program
817 	 * @return {@code true} when the merged layout is compatible with the active
818 	 *         program
819 	 */
820 	private boolean hasCompatiblePersistentGlobalLayout(long numGlobals) {
821 		Object[] globals = runtimeStack.getNumGlobals();
822 		if (!mergedGlobalLayoutActive
823 				|| globals == null
824 				|| globalVariableOffsets == null
825 				|| globals.length < numGlobals) {
826 			return false;
827 		}
828 		for (Map.Entry<String, Integer> entry : globalVariableOffsets.entrySet()) {
829 			int offset = entry.getValue().intValue();
830 			if (offset < 0 || offset >= globals.length || !entry.getKey().equals(runtimeStack.getGlobalName(offset))) {
831 				return false;
832 			}
833 		}
834 		return true;
835 	}
836 
837 	/**
838 	 * Applies execution-level initial variables to stack-managed globals.
839 	 * <p>
840 	 * Plain {@link #execute(AwkProgram, InputSource, List, Map)} calls apply all
841 	 * compatible globals here. Persistent executions only reapply non-persistent
842 	 * globals so carried user globals are not overwritten unless they were
843 	 * explicitly supplied for the current run.
844 	 *
845 	 * @param skipPersistentEligibleGlobals whether persistent user globals should
846 	 *        be skipped by this application pass
847 	 */
848 	private void applyExecutionInitialVariablesToGlobalSlots(boolean skipPersistentEligibleGlobals) {
849 		for (Map.Entry<String, Object> entry : executionInitialVariables.entrySet()) {
850 			String key = entry.getKey();
851 			if (skipPersistentEligibleGlobals && isPersistentEligibleGlobal(key)) {
852 				continue;
853 			}
854 			if (functionNames.contains(key)) {
855 				throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + key + ").");
856 			}
857 			Integer offsetObj = globalVariableOffsets.get(key);
858 			Boolean arrayObj = globalVariableArrays.get(key);
859 			if (offsetObj != null) {
860 				Object obj = normalizeExternalVariableValue(entry.getValue());
861 				if (arrayObj.booleanValue()) {
862 					if (obj instanceof Map) {
863 						runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
864 					} else {
865 						throw new IllegalArgumentException(
866 								"Cannot assign a scalar to a non-scalar variable (" + key + ").");
867 					}
868 				} else {
869 					runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
870 				}
871 			}
872 		}
873 	}
874 
875 	/**
876 	 * Prepares the per-execution runtime arguments and variable overrides.
877 	 * <p>
878 	 * Base settings-level variables remain the default source. Per-call overrides
879 	 * are layered on top without mutating the base snapshot held by this AVM.
880 	 *
881 	 * @param runtimeArguments name=value or filename entries for this execution
882 	 * @param variableOverrides per-call variable overrides for this execution
883 	 */
884 	private void prepareExecutionInputs(
885 			List<String> runtimeArguments,
886 			Map<String, Object> variableOverrides) {
887 		this.arguments = runtimeArguments != null ? new ArrayList<>(runtimeArguments) : Collections.<String>emptyList();
888 
889 		if (variableOverrides == null || variableOverrides.isEmpty()) {
890 			executionInitialVariables = baseInitialVariables;
891 			executionSpecialVariables = baseSpecialVariables;
892 		} else {
893 			executionInitialVariables = new HashMap<>(baseInitialVariables);
894 			executionInitialVariables.putAll(variableOverrides);
895 
896 			Map<String, Object> specialOverrides = JRT.copySpecialVariables(variableOverrides);
897 			if (specialOverrides.isEmpty()) {
898 				executionSpecialVariables = baseSpecialVariables;
899 			} else {
900 				executionSpecialVariables = new HashMap<>(baseSpecialVariables);
901 				executionSpecialVariables.putAll(specialOverrides);
902 			}
903 		}
904 	}
905 
906 	/**
907 	 * Filters the given map to retain only entries whose keys are
908 	 * persistent-eligible globals.
909 	 *
910 	 * @param source source map to filter
911 	 * @return insertion-ordered map containing only persistent-eligible entries
912 	 */
913 	private Map<String, Object> filterToPersistentEligible(Map<String, Object> source) {
914 		Map<String, Object> result = new LinkedHashMap<>();
915 		for (Map.Entry<String, Object> entry : source.entrySet()) {
916 			if (isPersistentEligibleGlobal(entry.getKey())) {
917 				result.put(entry.getKey(), entry.getValue());
918 			}
919 		}
920 		return result;
921 	}
922 
923 	/**
924 	 * Collects the current user-defined globals retained in the runtime stack.
925 	 *
926 	 * @return retained user globals keyed by name, in current runtime order
927 	 */
928 	private Map<String, Object> collectPersistentGlobalValues() {
929 		return filterToPersistentEligible(runtimeStack.snapshotGlobalVariables());
930 	}
931 
932 	/**
933 	 * Collects the AVM-wide baseline variables that should be reapplied before
934 	 * each persistent execution.
935 	 *
936 	 * @return baseline user globals keyed by name
937 	 */
938 	private Map<String, Object> collectBasePersistentGlobalSeeds() {
939 		Map<String, Object> basePersistentSeeds = new LinkedHashMap<>();
940 		for (Map.Entry<String, Object> entry : baseInitialVariables.entrySet()) {
941 			String name = entry.getKey();
942 			if (isPersistentEligibleGlobal(name)) {
943 				validateSeededGlobalName(name);
944 				Object value = normalizeExternalVariableValue(entry.getValue());
945 				validateSeededGlobalValue(name, value);
946 				basePersistentSeeds.put(name, value);
947 			}
948 		}
949 		return basePersistentSeeds;
950 	}
951 
952 	/**
953 	 * Collects the user-defined variables that should override the retained
954 	 * global bank for the current persistent execution.
955 	 * <p>
956 	 * Only user globals are included here. JRT-managed special variables still
957 	 * flow through the normal execution setup.
958 	 *
959 	 * @param variableOverrides per-call variable overrides for this execution
960 	 * @return insertion-ordered overriding seed values keyed by variable name
961 	 */
962 	private Map<String, Object> collectExecutionUserGlobalSeeds(Map<String, Object> variableOverrides) {
963 		Map<String, Object> executionUserSeeds = new LinkedHashMap<>();
964 		if (variableOverrides != null) {
965 			for (Map.Entry<String, Object> entry : variableOverrides.entrySet()) {
966 				String name = entry.getKey();
967 				if (isPersistentEligibleGlobal(name)) {
968 					validateSeededGlobalName(name);
969 					Object value = normalizeExternalVariableValue(entry.getValue());
970 					validateSeededGlobalValue(name, value);
971 					executionUserSeeds.put(name, value);
972 				}
973 			}
974 		}
975 		return executionUserSeeds;
976 	}
977 
978 	/**
979 	 * Builds the slot order for the next persistent execution.
980 	 * <p>
981 	 * The compiled globals are always installed first in their compiled offset
982 	 * order. Retained globals and seeded user globals that are not compiled by
983 	 * the incoming program are appended afterwards so future runs can still reuse
984 	 * them without changing the current program's compiled offsets.
985 	 *
986 	 * @param carriedGlobals retained user globals from the previous execution
987 	 * @param basePersistentSeeds baseline user globals coming from the AVM settings
988 	 * @param executionUserSeeds per-call user overrides for this execution
989 	 * @return merged slot-to-name layout for the next persistent run
990 	 */
991 	private List<String> buildMergedGlobalNamesByOffset(
992 			Map<String, Object> carriedGlobals,
993 			Map<String, Object> basePersistentSeeds,
994 			Map<String, Object> executionUserSeeds) {
995 		LinkedHashSet<String> orderedNames = new LinkedHashSet<>();
996 		List<Map.Entry<String, Integer>> compiledGlobals = new ArrayList<>(globalVariableOffsets.entrySet());
997 		compiledGlobals.sort(java.util.Comparator.comparingInt(Map.Entry::getValue));
998 		for (Map.Entry<String, Integer> entry : compiledGlobals) {
999 			orderedNames.add(entry.getKey());
1000 		}
1001 		orderedNames.addAll(carriedGlobals.keySet());
1002 		orderedNames.addAll(basePersistentSeeds.keySet());
1003 		orderedNames.addAll(executionUserSeeds.keySet());
1004 		return new ArrayList<>(orderedNames);
1005 	}
1006 
1007 	/**
1008 	 * Writes each entry from {@code globals} into the corresponding named slot in
1009 	 * the runtime stack.
1010 	 *
1011 	 * @param globals map of variable name to value to apply
1012 	 */
1013 	private void applyGlobalsToStack(Map<String, Object> globals) {
1014 		for (Map.Entry<String, Object> entry : globals.entrySet()) {
1015 			runtimeStack.setGlobalVariable(entry.getKey(), entry.getValue());
1016 		}
1017 	}
1018 
1019 	/**
1020 	 * Returns whether the given global name should participate in persistent
1021 	 * memory.
1022 	 *
1023 	 * @param name global variable name
1024 	 * @return {@code true} when the variable should persist across runs
1025 	 */
1026 	private boolean isPersistentEligibleGlobal(String name) {
1027 		return name != null
1028 				&& !isManagedSpecialVariable(name)
1029 				&& !NON_PERSISTENT_GLOBALS.contains(name);
1030 	}
1031 
1032 	/**
1033 	 * Returns whether the named variable is a JRT-managed special variable in
1034 	 * the current execution mode. Most special variables (NR, FS, FILENAME,
1035 	 * ...) always are. The gawk-only ERRNO and ARGIND are special outside
1036 	 * POSIX mode only: with {@code --posix} they are ordinary global
1037 	 * variables, exactly like {@code gawk --posix} treats them.
1038 	 *
1039 	 * @param name variable name to inspect
1040 	 * @return {@code true} when reads and writes of the variable go through
1041 	 *         the JRT instead of a global slot
1042 	 */
1043 	private boolean isManagedSpecialVariable(String name) {
1044 		if (!JRT.isJrtManagedSpecialVariable(name)) {
1045 			return false;
1046 		}
1047 		if (JRT.isGawkOnlySpecialVariable(name)) {
1048 			return !settings.isPosix();
1049 		}
1050 		return true;
1051 	}
1052 
1053 	/**
1054 	 * Validates that a seeded global name is compatible with the compiled
1055 	 * metadata of the current program before any value normalization can mutate a
1056 	 * caller-provided object graph.
1057 	 *
1058 	 * @param name variable name to validate
1059 	 */
1060 	private void validateSeededGlobalName(String name) {
1061 		if (functionNames.contains(name)) {
1062 			throw new IllegalArgumentException("Cannot assign a value to a function name (" + name + ").");
1063 		}
1064 	}
1065 
1066 	/**
1067 	 * Validates that a normalized seeded global value is compatible with the
1068 	 * compiled metadata of the current program.
1069 	 *
1070 	 * @param name variable name to validate
1071 	 * @param value proposed seeded value after normalization
1072 	 */
1073 	private void validateSeededGlobalValue(String name, Object value) {
1074 		Boolean arrayObj = globalVariableArrays.get(name);
1075 		if (Boolean.TRUE.equals(arrayObj) && !(value instanceof Map)) {
1076 			throw new IllegalArgumentException("Cannot assign a scalar to a non-scalar variable (" + name + ").");
1077 		}
1078 	}
1079 
1080 	/**
1081 	 * Executes the tuple stream after the runtime has been fully prepared.
1082 	 *
1083 	 * @param position current position in the tuple stream
1084 	 * @throws ExitException when the AWK program executes {@code exit}
1085 	 * @throws IOException when runtime input operations fail
1086 	 */
1087 	private void executeTuples(PositionTracker position)
1088 			throws ExitException,
1089 			IOException {
1090 		Map<Long, ConditionPair> conditionPairs = null;
1091 		Opcode opcode = null;
1092 		long tupleStartNanos = 0L;
1093 		try {
1094 			while (!position.isEOF()) {
1095 				// System_out.println("--> "+position);
1096 				Tuple tuple = position.current();
1097 				opcode = tuple.getOpcode();
1098 				if (profiling) {
1099 					tupleStartNanos = beforeProfiledTuple(tuple, opcode);
1100 				}
1101 				// switch on OPCODE
1102 				switch (opcode) {
1103 				case PRINT: {
1104 					execPrint((CountTuple) tuple);
1105 					position.next();
1106 					break;
1107 				}
1108 				case PRINT_TO_FILE: {
1109 					execPrintToFile((CountAndAppendTuple) tuple);
1110 					position.next();
1111 					break;
1112 				}
1113 				case PRINT_TO_PIPE: {
1114 					execPrintToPipe((CountTuple) tuple);
1115 					position.next();
1116 					break;
1117 				}
1118 				case PRINTF: {
1119 					execPrintf((CountTuple) tuple);
1120 					position.next();
1121 					break;
1122 				}
1123 				case PRINTF_TO_FILE: {
1124 					execPrintfToFile((CountAndAppendTuple) tuple);
1125 					position.next();
1126 					break;
1127 				}
1128 				case PRINTF_TO_PIPE: {
1129 					execPrintfToPipe((CountTuple) tuple);
1130 					position.next();
1131 					break;
1132 				}
1133 				case SPRINTF: {
1134 					// arg[0] = # of sprintf arguments
1135 					// stack[0] = arg1 (format string)
1136 					// stack[1] = arg2
1137 					// etc.
1138 					CountTuple countTuple = (CountTuple) tuple;
1139 					long numArgs = countTuple.getCount();
1140 					push(sprintfFunction(numArgs));
1141 					position.next();
1142 					break;
1143 				}
1144 				case LENGTH: {
1145 					execLength((CountTuple) tuple);
1146 					position.next();
1147 					break;
1148 				}
1149 				case PUSH_LONG: {
1150 					// arg[0] = long constant to push onto the stack
1151 					PushLongTuple pushTuple = (PushLongTuple) tuple;
1152 					push(pushTuple.getValue());
1153 					position.next();
1154 					break;
1155 				}
1156 				case PUSH_DOUBLE: {
1157 					// arg[0] = double constant to push onto the stack
1158 					PushDoubleTuple pushTuple = (PushDoubleTuple) tuple;
1159 					push(pushTuple.getValue());
1160 					position.next();
1161 					break;
1162 				}
1163 				case PUSH_STRING: {
1164 					// arg[0] = string constant to push onto the stack
1165 					PushStringTuple pushTuple = (PushStringTuple) tuple;
1166 					push(pushTuple.getValue());
1167 					position.next();
1168 					break;
1169 				}
1170 				case POP: {
1171 					// stack[0] = item to pop from the stack
1172 					Object discarded = pop();
1173 					if (tuple instanceof ScalarPopTuple) {
1174 						checkScalar(discarded);
1175 					}
1176 					position.next();
1177 					break;
1178 				}
1179 				case IFFALSE: {
1180 					// arg[0] = address to jump to if top of stack is false
1181 					// stack[0] = item to check
1182 
1183 					// if int, then check for 0
1184 					// if double, then check for 0
1185 					// if String, then check for "" or double value of "0"
1186 					boolean jump = !jrt.toBoolean(pop());
1187 					if (jump) {
1188 						position.jump(tuple.getAddress());
1189 					} else {
1190 						position.next();
1191 					}
1192 					break;
1193 				}
1194 				case TO_NUMBER: {
1195 					// stack[0] = item to convert to a number
1196 
1197 					// if int, then check for 0
1198 					// if double, then check for 0
1199 					// if String, then check for "" or double value of "0"
1200 					boolean val = jrt.toBoolean(pop());
1201 					push(val ? ONE : ZERO);
1202 					position.next();
1203 					break;
1204 				}
1205 				case IFTRUE: {
1206 					// arg[0] = address to jump to if top of stack is true
1207 					// stack[0] = item to check
1208 
1209 					// if int, then check for 0
1210 					// if double, then check for 0
1211 					// if String, then check for "" or double value of "0"
1212 					boolean jump = jrt.toBoolean(pop());
1213 					if (jump) {
1214 						position.jump(tuple.getAddress());
1215 					} else {
1216 						position.next();
1217 					}
1218 					break;
1219 				}
1220 				case NOT: {
1221 					// stack[0] = item to logically negate
1222 
1223 					Object o = pop();
1224 
1225 					boolean result = jrt.toBoolean(o);
1226 
1227 					if (result) {
1228 						push(0);
1229 					} else {
1230 						push(1);
1231 					}
1232 					position.next();
1233 					break;
1234 				}
1235 				case NEGATE: {
1236 					// stack[0] = item to numerically negate
1237 
1238 					double d = JRT.toDouble(pop());
1239 					push(-d);
1240 					position.next();
1241 					break;
1242 				}
1243 				case UNARY_PLUS: {
1244 					// stack[0] = item to convert to a number
1245 					double d = JRT.toDouble(pop());
1246 					push(d);
1247 					position.next();
1248 					break;
1249 				}
1250 				case GOTO: {
1251 					// arg[0] = address
1252 
1253 					position.jump(tuple.getAddress());
1254 					break;
1255 				}
1256 				case NOP: {
1257 					// do nothing, just advance the position
1258 					position.next();
1259 					break;
1260 				}
1261 				case CONCAT: {
1262 					// stack[0] = string1
1263 					// stack[1] = string2
1264 					String s2 = jrt.toAwkString(pop());
1265 					String s1 = jrt.toAwkString(pop());
1266 					String resultString = s1 + s2;
1267 					push(resultString);
1268 					position.next();
1269 					break;
1270 				}
1271 				case MULTI_CONCAT: {
1272 					// arg[0] = number of stack items to concatenate
1273 					// stack[0] = last concatenation operand
1274 					CountTuple countTuple = (CountTuple) tuple;
1275 					int count = (int) countTuple.getCount();
1276 					// Store String references so appends run left-to-right. Converting
1277 					// operands to char[] would copy them once before StringBuilder
1278 					// copies them again, and front-inserting would shift existing
1279 					// content on each operand.
1280 					String[] values = new String[count];
1281 					int resultLength = 0;
1282 					for (int i = count - 1; i >= 0; i--) {
1283 						values[i] = jrt.toAwkString(pop());
1284 						resultLength += values[i].length();
1285 					}
1286 					StringBuilder resultString = new StringBuilder(resultLength);
1287 					for (String value : values) {
1288 						resultString.append(value);
1289 					}
1290 					push(resultString.toString());
1291 					position.next();
1292 					break;
1293 				}
1294 				case ASSIGN:
1295 				case ASSIGN_NOPUSH: {
1296 					// arg[0] = offset
1297 					// arg[1] = isGlobal
1298 					// stack[0] = value
1299 					VariableTuple variableTuple = (VariableTuple) tuple;
1300 					Object value = pop();
1301 					assign(
1302 							variableTuple.getVariableOffset(),
1303 							value,
1304 							variableTuple.isGlobal(),
1305 							position,
1306 							opcode == Opcode.ASSIGN);
1307 					position.next();
1308 					break;
1309 				}
1310 				case ASSIGN_ARRAY: {
1311 					// arg[0] = offset
1312 					// arg[1] = isGlobal
1313 					// stack[0] = array index
1314 					// stack[1] = value
1315 					Object arrIdx = pop();
1316 					Object rhs = pop();
1317 					if (rhs == null) {
1318 						rhs = BLANK;
1319 					}
1320 					VariableTuple variableTuple = (VariableTuple) tuple;
1321 					long offset = variableTuple.getVariableOffset();
1322 					boolean isGlobal = variableTuple.isGlobal();
1323 					assignArray(offset, arrIdx, rhs, isGlobal);
1324 					position.next();
1325 					break;
1326 				}
1327 				case ASSIGN_MAP_ELEMENT: {
1328 					// stack[0] = array index
1329 					// stack[1] = associative array
1330 					// stack[2] = value
1331 					Object arrIdx = pop();
1332 					Map<Object, Object> array = toMap(pop());
1333 					Object rhs = pop();
1334 					if (rhs == null) {
1335 						rhs = BLANK;
1336 					}
1337 					assignMapElement(array, arrIdx, rhs);
1338 					position.next();
1339 					break;
1340 				}
1341 				case PLUS_EQ_ARRAY:
1342 				case MINUS_EQ_ARRAY:
1343 				case MULT_EQ_ARRAY:
1344 				case DIV_EQ_ARRAY:
1345 				case MOD_EQ_ARRAY:
1346 				case POW_EQ_ARRAY: {
1347 					// arg[0] = offset
1348 					// arg[1] = isGlobal
1349 					// stack[0] = array index
1350 					// stack[1] = value
1351 					Object arrIdx = pop();
1352 					Object rhs = pop();
1353 					if (rhs == null) {
1354 						rhs = BLANK;
1355 					}
1356 					VariableTuple variableTuple = (VariableTuple) tuple;
1357 					long offset = variableTuple.getVariableOffset();
1358 					boolean isGlobal = variableTuple.isGlobal();
1359 
1360 					double val = JRT.toDouble(rhs);
1361 
1362 					Map<Object, Object> array = ensureMapVariable(offset, isGlobal);
1363 					checkScalar(arrIdx);
1364 					Object o = array.get(arrIdx);
1365 					double origVal = JRT.toDouble(o);
1366 
1367 					double newVal;
1368 
1369 					switch (opcode) {
1370 					case PLUS_EQ_ARRAY:
1371 						newVal = origVal + val;
1372 						break;
1373 					case MINUS_EQ_ARRAY:
1374 						newVal = origVal - val;
1375 						break;
1376 					case MULT_EQ_ARRAY:
1377 						newVal = origVal * val;
1378 						break;
1379 					case DIV_EQ_ARRAY:
1380 						newVal = origVal / val;
1381 						break;
1382 					case MOD_EQ_ARRAY:
1383 						newVal = origVal % val;
1384 						break;
1385 					case POW_EQ_ARRAY:
1386 						newVal = Math.pow(origVal, val);
1387 						break;
1388 					default:
1389 						throw new Error("Invalid op code here: " + opcode);
1390 					}
1391 
1392 					assignArray(offset, arrIdx, newVal, isGlobal);
1393 					position.next();
1394 					break;
1395 				}
1396 				case PLUS_EQ_MAP_ELEMENT:
1397 				case MINUS_EQ_MAP_ELEMENT:
1398 				case MULT_EQ_MAP_ELEMENT:
1399 				case DIV_EQ_MAP_ELEMENT:
1400 				case MOD_EQ_MAP_ELEMENT:
1401 				case POW_EQ_MAP_ELEMENT: {
1402 					// stack[0] = array index
1403 					// stack[1] = associative array
1404 					// stack[2] = value
1405 					Object arrIdx = pop();
1406 					Map<Object, Object> array = toMap(pop());
1407 					Object rhs = pop();
1408 					if (rhs == null) {
1409 						rhs = BLANK;
1410 					}
1411 
1412 					double val = JRT.toDouble(rhs);
1413 					checkScalar(arrIdx);
1414 					Object o = array.get(arrIdx);
1415 					double origVal = JRT.toDouble(o);
1416 					double newVal;
1417 
1418 					switch (opcode) {
1419 					case PLUS_EQ_MAP_ELEMENT:
1420 						newVal = origVal + val;
1421 						break;
1422 					case MINUS_EQ_MAP_ELEMENT:
1423 						newVal = origVal - val;
1424 						break;
1425 					case MULT_EQ_MAP_ELEMENT:
1426 						newVal = origVal * val;
1427 						break;
1428 					case DIV_EQ_MAP_ELEMENT:
1429 						newVal = origVal / val;
1430 						break;
1431 					case MOD_EQ_MAP_ELEMENT:
1432 						newVal = origVal % val;
1433 						break;
1434 					case POW_EQ_MAP_ELEMENT:
1435 						newVal = Math.pow(origVal, val);
1436 						break;
1437 					default:
1438 						throw new Error("Invalid op code here: " + opcode);
1439 					}
1440 
1441 					assignMapElement(array, arrIdx, newVal);
1442 					position.next();
1443 					break;
1444 				}
1445 
1446 				case ASSIGN_AS_INPUT: {
1447 					// stack[0] = value
1448 					jrt.setInputLine(pop());
1449 					push(jrt.getInputLine());
1450 					position.next();
1451 					break;
1452 				}
1453 
1454 				case ASSIGN_AS_INPUT_FIELD: {
1455 					// stack[0] = field number
1456 					// stack[1] = value
1457 					Object fieldNumObj = pop();
1458 					long fieldNum = JRT.parseFieldNumber(fieldNumObj);
1459 					Object value = pop();
1460 					push(value); // leave the result on the stack
1461 					if (fieldNum == 0) {
1462 						jrt.setInputLine(value);
1463 						jrt.jrtParseFields();
1464 					} else {
1465 						jrt.jrtSetInputField(value, fieldNum);
1466 					}
1467 					position.next();
1468 					break;
1469 				}
1470 				case PLUS_EQ:
1471 				case MINUS_EQ:
1472 				case MULT_EQ:
1473 				case DIV_EQ:
1474 				case MOD_EQ:
1475 				case POW_EQ: {
1476 					// arg[0] = offset
1477 					// arg[1] = isGlobal
1478 					// stack[0] = value
1479 					VariableTuple variableTuple = (VariableTuple) tuple;
1480 					long offset = variableTuple.getVariableOffset();
1481 					boolean isGlobal = variableTuple.isGlobal();
1482 					Object o1 = resolveVariable(offset, isGlobal, false);
1483 					Object o2 = pop();
1484 					double d1 = JRT.toDouble(o1);
1485 					double d2 = JRT.toDouble(o2);
1486 					double ans;
1487 					switch (opcode) {
1488 					case PLUS_EQ:
1489 						ans = d1 + d2;
1490 						break;
1491 					case MINUS_EQ:
1492 						ans = d1 - d2;
1493 						break;
1494 					case MULT_EQ:
1495 						ans = d1 * d2;
1496 						break;
1497 					case DIV_EQ:
1498 						ans = d1 / d2;
1499 						break;
1500 					case MOD_EQ:
1501 						ans = d1 % d2;
1502 						break;
1503 					case POW_EQ:
1504 						ans = Math.pow(d1, d2);
1505 						break;
1506 					default:
1507 						throw new Error("Invalid opcode here: " + opcode);
1508 					}
1509 					push(ans);
1510 					runtimeStack.setVariable(offset, ans, isGlobal);
1511 					position.next();
1512 					break;
1513 				}
1514 				case PLUS_EQ_INPUT_FIELD:
1515 				case MINUS_EQ_INPUT_FIELD:
1516 				case MULT_EQ_INPUT_FIELD:
1517 				case DIV_EQ_INPUT_FIELD:
1518 				case MOD_EQ_INPUT_FIELD:
1519 				case POW_EQ_INPUT_FIELD: {
1520 					// stack[0] = dollar_fieldNumber
1521 					// stack[1] = inc value
1522 
1523 					// same code as GET_INPUT_FIELD:
1524 					long fieldnum = JRT.parseFieldNumber(pop());
1525 					double incval = JRT.toDouble(pop());
1526 
1527 					// except here, get the number, and add the incvalue
1528 					Object numObj = jrt.jrtGetInputField(fieldnum);
1529 					double num;
1530 					switch (opcode) {
1531 					case PLUS_EQ_INPUT_FIELD:
1532 						num = JRT.toDouble(numObj) + incval;
1533 						break;
1534 					case MINUS_EQ_INPUT_FIELD:
1535 						num = JRT.toDouble(numObj) - incval;
1536 						break;
1537 					case MULT_EQ_INPUT_FIELD:
1538 						num = JRT.toDouble(numObj) * incval;
1539 						break;
1540 					case DIV_EQ_INPUT_FIELD:
1541 						num = JRT.toDouble(numObj) / incval;
1542 						break;
1543 					case MOD_EQ_INPUT_FIELD:
1544 						num = JRT.toDouble(numObj) % incval;
1545 						break;
1546 					case POW_EQ_INPUT_FIELD:
1547 						num = Math.pow(JRT.toDouble(numObj), incval);
1548 						break;
1549 					default:
1550 						throw new Error("Invalid opcode here: " + opcode);
1551 					}
1552 					setNumOnJRT(fieldnum, num);
1553 
1554 					// put the result value on the stack
1555 					push(num);
1556 					position.next();
1557 
1558 					break;
1559 				}
1560 				case INC: {
1561 					// arg[0] = offset
1562 					// arg[1] = isGlobal
1563 					VariableTuple variableTuple = (VariableTuple) tuple;
1564 					inc(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1565 					position.next();
1566 					break;
1567 				}
1568 				case DEC: {
1569 					// arg[0] = offset
1570 					// arg[1] = isGlobal
1571 					VariableTuple variableTuple = (VariableTuple) tuple;
1572 					dec(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1573 					position.next();
1574 					break;
1575 				}
1576 				case POSTINC: {
1577 					// arg[0] = offset
1578 					// arg[1] = isGlobal
1579 					pop();
1580 					VariableTuple variableTuple = (VariableTuple) tuple;
1581 					push(inc(variableTuple.getVariableOffset(), variableTuple.isGlobal()));
1582 					position.next();
1583 					break;
1584 				}
1585 				case POSTDEC: {
1586 					// arg[0] = offset
1587 					// arg[1] = isGlobal
1588 					pop();
1589 					VariableTuple variableTuple = (VariableTuple) tuple;
1590 					push(dec(variableTuple.getVariableOffset(), variableTuple.isGlobal()));
1591 					position.next();
1592 					break;
1593 				}
1594 				case INC_ARRAY_REF: {
1595 					// arg[0] = offset
1596 					// arg[1] = isGlobal
1597 					// stack[0] = array index
1598 					VariableTuple variableTuple = (VariableTuple) tuple;
1599 					boolean isGlobal = variableTuple.isGlobal();
1600 					Map<Object, Object> aa = ensureMapVariable(variableTuple.getVariableOffset(), isGlobal);
1601 					Object key = pop();
1602 					checkScalar(key);
1603 					Object o = aa.get(key);
1604 					double ans = JRT.toDouble(o) + 1;
1605 					aa.put(key, ans);
1606 					position.next();
1607 					break;
1608 				}
1609 				case DEC_ARRAY_REF: {
1610 					// arg[0] = offset
1611 					// arg[1] = isGlobal
1612 					// stack[0] = array index
1613 					VariableTuple variableTuple = (VariableTuple) tuple;
1614 					boolean isGlobal = variableTuple.isGlobal();
1615 					Map<Object, Object> aa = ensureMapVariable(variableTuple.getVariableOffset(), isGlobal);
1616 					Object key = pop();
1617 					checkScalar(key);
1618 					Object o = aa.get(key);
1619 					double ans = JRT.toDouble(o) - 1;
1620 					aa.put(key, ans);
1621 					position.next();
1622 					break;
1623 				}
1624 				case INC_MAP_REF: {
1625 					// stack[0] = array index
1626 					// stack[1] = associative array
1627 					Object key = pop();
1628 					checkScalar(key);
1629 					Map<Object, Object> aa = toMap(pop());
1630 					Object o = aa.get(key);
1631 					double ans = JRT.toDouble(o) + 1;
1632 					aa.put(key, ans);
1633 					position.next();
1634 					break;
1635 				}
1636 				case DEC_MAP_REF: {
1637 					// stack[0] = array index
1638 					// stack[1] = associative array
1639 					Object key = pop();
1640 					checkScalar(key);
1641 					Map<Object, Object> aa = toMap(pop());
1642 					Object o = aa.get(key);
1643 					double ans = JRT.toDouble(o) - 1;
1644 					aa.put(key, ans);
1645 					position.next();
1646 					break;
1647 				}
1648 				case INC_DOLLAR_REF: {
1649 					// stack[0] = dollar index (field number)
1650 					long fieldnum = JRT.parseFieldNumber(pop());
1651 
1652 					Object numObj = jrt.jrtGetInputField(fieldnum);
1653 					double original = JRT.toDouble(numObj);
1654 					double num = original + 1;
1655 					setNumOnJRT(fieldnum, num);
1656 
1657 					push(Double.valueOf(original));
1658 
1659 					position.next();
1660 					break;
1661 				}
1662 				case DEC_DOLLAR_REF: {
1663 					// stack[0] = dollar index (field number)
1664 					// same code as GET_INPUT_FIELD:
1665 					long fieldnum = JRT.parseFieldNumber(pop());
1666 
1667 					Object numObj = jrt.jrtGetInputField(fieldnum);
1668 					double original = JRT.toDouble(numObj);
1669 					double num = original - 1;
1670 					setNumOnJRT(fieldnum, num);
1671 
1672 					push(Double.valueOf(original));
1673 
1674 					position.next();
1675 					break;
1676 				}
1677 				case DEREFERENCE: {
1678 					// arg[0] = offset
1679 					// arg[1] = isGlobal
1680 					DereferenceTuple dereferenceTuple = (DereferenceTuple) tuple;
1681 					boolean isGlobal = dereferenceTuple.isGlobal();
1682 					long offset = dereferenceTuple.getVariableOffset();
1683 					push(resolveVariable(offset, isGlobal, dereferenceTuple.isArray()));
1684 					position.next();
1685 					break;
1686 				}
1687 				case PEEK_DEREFERENCE: {
1688 					VariableTuple variableTuple = (VariableTuple) tuple;
1689 					Object value = runtimeStack
1690 							.getVariable(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1691 					push(
1692 							value instanceof ArgumentReference ?
1693 									resolveRawArgumentReference((ArgumentReference) value) : value);
1694 					position.next();
1695 					break;
1696 				}
1697 				case PUSH_INDIRECT_ARGUMENT: {
1698 					VariableTuple variableTuple = (VariableTuple) tuple;
1699 					Object scalarValue = runtimeStack
1700 							.getVariable(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1701 					// Typed values are passed as plain values; only untyped
1702 					// variables need a live link back to the caller's slot.
1703 					if (scalarValue instanceof ArgumentReference) {
1704 						push(resolveUserFunctionArgument(scalarValue));
1705 					} else if (isUntyped(scalarValue)) {
1706 						push(
1707 								new IndirectArgumentReference(
1708 										runtimeStack.getVariableFrame(variableTuple.isGlobal()),
1709 										variableTuple.getVariableOffset(),
1710 										scalarValue));
1711 					} else {
1712 						push(scalarValue);
1713 					}
1714 					position.next();
1715 					break;
1716 				}
1717 				case PUSH_INDIRECT_ARRAY_ARGUMENT: {
1718 					Object idx = pop();
1719 					checkScalar(idx);
1720 					Map<Object, Object> map = toMap(pop());
1721 					Object scalarValue = JRT.getAssocArrayValue(map, idx);
1722 					// Typed elements are passed as plain values; only untyped
1723 					// elements need a live link back to their containing array.
1724 					push(
1725 							isUntyped(scalarValue) ?
1726 									new IndirectArrayArgumentReference(map, idx, scalarValue) : scalarValue);
1727 					position.next();
1728 					break;
1729 				}
1730 				case DEREF_ARRAY: {
1731 					// stack[0] = array index
1732 					Object idx = pop(); // idx
1733 					checkScalar(idx);
1734 					Map<Object, Object> map = toMap(pop());
1735 					Object o = JRT.getAssocArrayValue(map, idx);
1736 					push(o);
1737 					position.next();
1738 					break;
1739 				}
1740 				case ENSURE_ARRAY_ELEMENT: {
1741 					// stack[0] = array index
1742 					// stack[1] = associative array
1743 					Object idx = pop();
1744 					Map<Object, Object> map = toMap(pop());
1745 					push(ensureArrayInArray(map, idx));
1746 					position.next();
1747 					break;
1748 				}
1749 				case PEEK_ARRAY_ELEMENT: {
1750 					// stack[0] = array index
1751 					Object idx = pop();
1752 					checkScalar(idx);
1753 					Map<Object, Object> map = toMap(pop());
1754 					if (map instanceof AssocArray && !JRT.containsAwkKey(map, idx)) {
1755 						push(AssocArray.UNTYPED);
1756 					} else {
1757 						Object value = map.get(idx);
1758 						push(value != null ? value : AssocArray.UNTYPED);
1759 					}
1760 					position.next();
1761 					break;
1762 				}
1763 				case SRAND: {
1764 					// arg[0] = numArgs (where 0 = no args, anything else = one argument)
1765 					// stack[0] = seed (only if numArgs != 0)
1766 					CountTuple countTuple = (CountTuple) tuple;
1767 					long numArgs = countTuple.getCount();
1768 					int seed;
1769 					if (numArgs == 0) {
1770 						// use the time of day for the seed
1771 						seed = JRT.timeSeed();
1772 					} else {
1773 						seed = (int) JRT.toDouble(pop());
1774 					}
1775 					int previousSeed = randomNumberGenerator.getSeed();
1776 					randomNumberGenerator.setSeed(seed);
1777 					push(previousSeed);
1778 					position.next();
1779 					break;
1780 				}
1781 				case RAND: {
1782 					push(randomNumberGenerator.nextDouble());
1783 					position.next();
1784 					break;
1785 				}
1786 				case INTFUNC: {
1787 					// stack[0] = arg to int() function
1788 					push((long) JRT.toDouble(pop()));
1789 					position.next();
1790 					break;
1791 				}
1792 				case SQRT: {
1793 					// stack[0] = arg to sqrt() function
1794 					push(Math.sqrt(JRT.toDouble(pop())));
1795 					position.next();
1796 					break;
1797 				}
1798 				case LOG: {
1799 					// stack[0] = arg to log() function
1800 					push(Math.log(JRT.toDouble(pop())));
1801 					position.next();
1802 					break;
1803 				}
1804 				case EXP: {
1805 					// stack[0] = arg to exp() function
1806 					push(Math.exp(JRT.toDouble(pop())));
1807 					position.next();
1808 					break;
1809 				}
1810 				case SIN: {
1811 					// stack[0] = arg to sin() function
1812 					push(Math.sin(JRT.toDouble(pop())));
1813 					position.next();
1814 					break;
1815 				}
1816 				case COS: {
1817 					// stack[0] = arg to cos() function
1818 					push(Math.cos(JRT.toDouble(pop())));
1819 					position.next();
1820 					break;
1821 				}
1822 				case ATAN2: {
1823 					// stack[0] = 2nd arg to atan2() function
1824 					// stack[1] = 1st arg to atan2() function
1825 					double d2 = JRT.toDouble(pop());
1826 					double d1 = JRT.toDouble(pop());
1827 					push(Math.atan2(d1, d2));
1828 					position.next();
1829 					break;
1830 				}
1831 				case MATCH: {
1832 					execMatch();
1833 					position.next();
1834 					break;
1835 				}
1836 				case INDEX: {
1837 					// stack[0] = 2nd arg to index() function
1838 					// stack[1] = 1st arg to index() function
1839 					String s2 = jrt.toAwkString(pop());
1840 					String s1 = jrt.toAwkString(pop());
1841 					push(jrt.index(s1, s2));
1842 					position.next();
1843 					break;
1844 				}
1845 				case SUB_FOR_DOLLAR_0: {
1846 					execSubForDollar0((BooleanTuple) tuple);
1847 					position.next();
1848 					break;
1849 				}
1850 				case SUB_FOR_DOLLAR_REFERENCE: {
1851 					execSubForDollarReference((BooleanTuple) tuple);
1852 					position.next();
1853 					break;
1854 				}
1855 				case SUB_FOR_VARIABLE: {
1856 					execSubForVariable((SubstitutionVariableTuple) tuple, position);
1857 					position.next();
1858 					break;
1859 				}
1860 				case SUB_FOR_ARRAY_REFERENCE: {
1861 					execSubForArrayReference((SubstitutionVariableTuple) tuple);
1862 					position.next();
1863 					break;
1864 				}
1865 				case SUB_FOR_MAP_REFERENCE: {
1866 					execSubForMapReference((BooleanTuple) tuple);
1867 					position.next();
1868 					break;
1869 				}
1870 				case SPLIT: {
1871 					execSplit((CountTuple) tuple, position);
1872 					position.next();
1873 					break;
1874 				}
1875 				case SUBSTR: {
1876 					execSubstr((CountTuple) tuple);
1877 					position.next();
1878 					break;
1879 				}
1880 				case TOLOWER: {
1881 					// stack[0] = string
1882 					push(jrt.toAwkString(pop()).toLowerCase());
1883 					position.next();
1884 					break;
1885 				}
1886 				case TOUPPER: {
1887 					// stack[0] = string
1888 					push(jrt.toAwkString(pop()).toUpperCase());
1889 					position.next();
1890 					break;
1891 				}
1892 				case SYSTEM: {
1893 					// stack[0] = command string
1894 					String s = jrt.toAwkString(pop());
1895 					push(jrt.jrtSystem(s));
1896 					position.next();
1897 					break;
1898 				}
1899 				case SWAP: {
1900 					// stack[0] = item1
1901 					// stack[1] = item2
1902 					Object o1 = pop();
1903 					Object o2 = pop();
1904 					push(o1);
1905 					push(o2);
1906 					position.next();
1907 					break;
1908 				}
1909 				case CMP_EQ: {
1910 					// stack[0] = item2
1911 					// stack[1] = item1
1912 					Object o2 = pop();
1913 					Object o1 = pop();
1914 					push(JRT.compare2(o1, o2, 0, jrt.isIgnoreCase()) ? ONE : ZERO);
1915 					position.next();
1916 					break;
1917 				}
1918 				case CMP_LT: {
1919 					// stack[0] = item2
1920 					// stack[1] = item1
1921 					Object o2 = pop();
1922 					Object o1 = pop();
1923 					push(JRT.compare2(o1, o2, -1, jrt.isIgnoreCase()) ? ONE : ZERO);
1924 					position.next();
1925 					break;
1926 				}
1927 				case CMP_GT: {
1928 					// stack[0] = item2
1929 					// stack[1] = item1
1930 					Object o2 = pop();
1931 					Object o1 = pop();
1932 					push(JRT.compare2(o1, o2, 1, jrt.isIgnoreCase()) ? ONE : ZERO);
1933 					position.next();
1934 					break;
1935 				}
1936 				case MATCHES: {
1937 					// stack[0] = regexp (precompiled or dynamic)
1938 					// stack[1] = text
1939 					Object o2 = pop();
1940 					Object o1 = pop();
1941 					push(jrt.matches(o1.toString(), o2) ? 1 : 0);
1942 					position.next();
1943 					break;
1944 				}
1945 				case ADD: {
1946 					// stack[0] = item2
1947 					// stack[1] = item1
1948 					Object o2 = pop();
1949 					Object o1 = pop();
1950 					double d1 = JRT.toDouble(o1);
1951 					double d2 = JRT.toDouble(o2);
1952 					double ans = d1 + d2;
1953 					push(ans);
1954 					position.next();
1955 					break;
1956 				}
1957 				case SUBTRACT: {
1958 					// stack[0] = item2
1959 					// stack[1] = item1
1960 					Object o2 = pop();
1961 					Object o1 = pop();
1962 					double d1 = JRT.toDouble(o1);
1963 					double d2 = JRT.toDouble(o2);
1964 					double ans = d1 - d2;
1965 					push(ans);
1966 					position.next();
1967 					break;
1968 				}
1969 				case MULTIPLY: {
1970 					// stack[0] = item2
1971 					// stack[1] = item1
1972 					Object o2 = pop();
1973 					Object o1 = pop();
1974 					double d1 = JRT.toDouble(o1);
1975 					double d2 = JRT.toDouble(o2);
1976 					double ans = d1 * d2;
1977 					push(ans);
1978 					position.next();
1979 					break;
1980 				}
1981 				case DIVIDE: {
1982 					// stack[0] = item2
1983 					// stack[1] = item1
1984 					Object o2 = pop();
1985 					Object o1 = pop();
1986 					double d1 = JRT.toDouble(o1);
1987 					double d2 = JRT.toDouble(o2);
1988 					double ans = d1 / d2;
1989 					push(ans);
1990 					position.next();
1991 					break;
1992 				}
1993 				case MOD: {
1994 					// stack[0] = item2
1995 					// stack[1] = item1
1996 					Object o2 = pop();
1997 					Object o1 = pop();
1998 					double d1 = JRT.toDouble(o1);
1999 					double d2 = JRT.toDouble(o2);
2000 					double ans = d1 % d2;
2001 					push(ans);
2002 					position.next();
2003 					break;
2004 				}
2005 				case POW: {
2006 					// stack[0] = item2
2007 					// stack[1] = item1
2008 					Object o2 = pop();
2009 					Object o1 = pop();
2010 					double d1 = JRT.toDouble(o1);
2011 					double d2 = JRT.toDouble(o2);
2012 					double ans = Math.pow(d1, d2);
2013 					push(ans);
2014 					position.next();
2015 					break;
2016 				}
2017 				case DUP: {
2018 					// stack[0] = top of stack item
2019 					Object o = pop();
2020 					push(o);
2021 					push(o);
2022 					position.next();
2023 					break;
2024 				}
2025 				case KEYLIST: {
2026 					Object o = pop();
2027 					if (isUntyped(o)) {
2028 						push(new ArrayDeque<>());
2029 						position.next();
2030 						break;
2031 					}
2032 					if (!(o instanceof Map)) {
2033 						throw new AwkRuntimeException("Attempting to use a scalar as an array.");
2034 					}
2035 					@SuppressWarnings("unchecked")
2036 					Map<Object, Object> map = (Map<Object, Object>) o;
2037 					push(new ArrayDeque<>(forInKeyOrder == null ? map.keySet() : forInKeyOrder.order(map)));
2038 					position.next();
2039 					break;
2040 				}
2041 				case IS_EMPTY_KEYLIST: {
2042 					// arg[0] = address
2043 					// stack[0] = Deque
2044 					Object o = pop();
2045 					if (o == null || !(o instanceof Deque)) {
2046 						throw new AwkRuntimeException(
2047 								position.lineNumber(),
2048 								"Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
2049 					}
2050 					Deque<?> keylist = (Deque<?>) o;
2051 					if (keylist.isEmpty()) {
2052 						position.jump(tuple.getAddress());
2053 					} else {
2054 						position.next();
2055 					}
2056 					break;
2057 				}
2058 				case GET_FIRST_AND_REMOVE_FROM_KEYLIST: {
2059 					// stack[0] = Deque
2060 					Object o = pop();
2061 					if (o == null || !(o instanceof Deque)) {
2062 						throw new AwkRuntimeException(
2063 								position.lineNumber(),
2064 								"Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
2065 					}
2066 					// pop off and return the head of the key set
2067 					Deque<?> keylist = (Deque<?>) o;
2068 					push(keylist.removeFirst());
2069 					position.next();
2070 					break;
2071 				}
2072 				case CHECK_CLASS: {
2073 					// arg[0] = class object
2074 					// stack[0] = item to check
2075 					ClassTuple checkTuple = (ClassTuple) tuple;
2076 					Object o = pop();
2077 					if (!checkTuple.getType().isInstance(o)) {
2078 						throw new AwkRuntimeException(
2079 								position.lineNumber(),
2080 								"Verification failed. Top-of-stack = " + o.getClass() + " isn't an instance of "
2081 										+ checkTuple.getType());
2082 					}
2083 					push(o);
2084 					position.next();
2085 					break;
2086 				}
2087 				case CONSUME_INPUT: {
2088 					// arg[0] = address
2089 					// store the next record into $0, $1, ...
2090 					if (jrt.consumeInput(resolvedInputSource)) {
2091 						position.next();
2092 					} else {
2093 						position.jump(tuple.getAddress());
2094 					}
2095 					break;
2096 				}
2097 				case CONSUME_FILE_INPUT: {
2098 					// arg[0] = address of the ENDFILE section
2099 					// store the next record of the current file into $0, $1, ...
2100 					withinBeginFileBlocks = false;
2101 					if (jrt.consumeCurrentFileInput(resolvedInputSource)) {
2102 						position.next();
2103 					} else {
2104 						withinEndFileBlocks = true;
2105 						position.jump(tuple.getAddress());
2106 					}
2107 					break;
2108 				}
2109 				case NEXT_FILE: {
2110 					// arg[0] = address to jump to when no input file remains
2111 					inputFileLoopStarted = true;
2112 					withinEndFileBlocks = false;
2113 					if (jrt.advanceToNextFile(resolvedInputSource)) {
2114 						withinBeginFileBlocks = true;
2115 						position.next();
2116 					} else {
2117 						position.jump(tuple.getAddress());
2118 					}
2119 					break;
2120 				}
2121 				case EXEC_NEXTFILE: {
2122 					executeNextfile(position);
2123 					break;
2124 				}
2125 
2126 				case GETLINE_INPUT: {
2127 					checkGetlineAllowed(position);
2128 					boolean consumed = isMainInputFileBounded() ?
2129 							jrt.consumeCurrentFileInput(resolvedInputSource) : jrt.consumeInput(resolvedInputSource);
2130 					push(consumed ? 1 : 0);
2131 					position.next();
2132 					break;
2133 				}
2134 				case GETLINE_INPUT_TO_TARGET: {
2135 					checkGetlineAllowed(position);
2136 					Object input = isMainInputFileBounded() ?
2137 							jrt.consumeCurrentFileInputToTarget(resolvedInputSource) : jrt.consumeInputToTarget(resolvedInputSource);
2138 					if (input != null) {
2139 						push(1);
2140 						push(input);
2141 					} else {
2142 						push(0);
2143 						push("");
2144 					}
2145 					position.next();
2146 					break;
2147 				}
2148 				case USE_AS_FILE_INPUT: {
2149 					// stack[0] = filename
2150 					String s = jrt.toAwkString(pop());
2151 					if (jrt.jrtConsumeFileInput(s)) {
2152 						push(1);
2153 						push(jrt.getInputLine());
2154 					} else {
2155 						push(0);
2156 						push("");
2157 					}
2158 					position.next();
2159 					break;
2160 				}
2161 				case USE_AS_COMMAND_INPUT: {
2162 					// stack[0] = command line
2163 					String s = jrt.toAwkString(pop());
2164 					if (jrt.jrtConsumeCommandInput(s)) {
2165 						push(1);
2166 						push(jrt.getInputLine());
2167 					} else {
2168 						push(0);
2169 						push("");
2170 					}
2171 					position.next();
2172 					break;
2173 				}
2174 				case ENVIRON_OFFSET: {
2175 					// arg[0] = offset; already populated from SET_NUM_GLOBALS so
2176 					// beforeStart hooks observe the real value
2177 					populateEnviron(((LongTuple) tuple).getValue());
2178 					position.next();
2179 					break;
2180 				}
2181 				case ARGC_OFFSET: {
2182 					// arg[0] = offset; already populated from SET_NUM_GLOBALS
2183 					populateArgc(((LongTuple) tuple).getValue());
2184 					position.next();
2185 					break;
2186 				}
2187 				case ARGV_OFFSET: {
2188 					// arg[0] = offset; already populated from SET_NUM_GLOBALS
2189 					populateArgv(((LongTuple) tuple).getValue());
2190 					position.next();
2191 					break;
2192 				}
2193 				case GET_INPUT_FIELD: {
2194 					// stack[0] = field number
2195 					Object fieldNumber = pop();
2196 					push(jrt.jrtGetInputField(fieldNumber));
2197 					position.next();
2198 					break;
2199 				}
2200 				case GET_INPUT_FIELD_CONST: {
2201 					InputFieldTuple inputFieldTuple = (InputFieldTuple) tuple;
2202 					long fieldnum = inputFieldTuple.getFieldIndex();
2203 					push(jrt.jrtGetInputField(fieldnum));
2204 					position.next();
2205 					break;
2206 				}
2207 				case APPLY_RS: {
2208 					jrt.applyRS(jrt.getRSVar());
2209 					position.next();
2210 					break;
2211 				}
2212 				case CALL_FUNCTION: {
2213 					// arg[0] = function address
2214 					// arg[1] = function name
2215 					// arg[2] = # of formal parameters
2216 					// arg[3] = # of actual parameters
2217 					// stack[0] = last actual parameter
2218 					// stack[1] = before-last actual parameter
2219 					// ...
2220 					// stack[n-1] = first actual parameter
2221 					// etc.
2222 					CallFunctionTuple callTuple = (CallFunctionTuple) tuple;
2223 					Address funcAddr = callTuple.getAddress();
2224 					long numFormalParams = callTuple.getNumFormalParams();
2225 					long numActualParams = callTuple.getNumActualParams();
2226 					runtimeStack.pushFrame(numFormalParams, position.currentIndex());
2227 					// Arguments are stacked, so first in the stack is the last for the function
2228 					for (long i = numActualParams - 1; i >= 0; i--) {
2229 						Object argument = pop();
2230 						adoptElementArgumentReference(argument);
2231 						runtimeStack.setVariable(i, argument, false); // false = local
2232 					}
2233 					position.jump(funcAddr);
2234 					// position.next();
2235 					break;
2236 				}
2237 				case INDIRECT_CALL: {
2238 					IndirectCallTuple callTuple = (IndirectCallTuple) tuple;
2239 					Object[] actualArguments = popArguments(callTuple.getNumActualParams());
2240 					String requestedName = jrt.toAwkString(pop());
2241 					String qualifiedName = normalizeIndirectFunctionName(requestedName);
2242 					IndirectFunctionTarget target = callTuple.getUserFunctions().get(qualifiedName);
2243 					if (target != null) {
2244 						long formalCount = target.getNumFormalParams();
2245 						if (actualArguments.length > formalCount) {
2246 							jrt
2247 									.printWarning(
2248 											"gawk: "
2249 													+ callTuple.getSourceName()
2250 													+ ":"
2251 													+ callTuple.getSourceLine()
2252 													+ ": warning: function `"
2253 													+ qualifiedName
2254 													+ "' called with more arguments than declared");
2255 						}
2256 						if (profiling) {
2257 							activeProfilingFunctions.push(new ActiveFunction(qualifiedName, tupleStartNanos));
2258 						}
2259 						runtimeStack.pushFrame(formalCount, position.currentIndex());
2260 						adoptElementArgumentReferences(actualArguments);
2261 						int copiedArgumentCount = Math.min(actualArguments.length, (int) formalCount);
2262 						for (int i = 0; i < copiedArgumentCount; i++) {
2263 							runtimeStack.setVariable(i, actualArguments[i], false);
2264 						}
2265 						position.jump(target.getAddress());
2266 						break;
2267 					}
2268 
2269 					String awkName = requestedName.startsWith("awk::") ?
2270 							requestedName.substring("awk::".length()) : requestedName;
2271 					BuiltinFunction builtin = BuiltinFunction.of(awkName);
2272 					if (builtin != null) {
2273 						resolveIndirectArguments(actualArguments, builtin);
2274 						push(invokeIndirectBuiltin(builtin, actualArguments, position.lineNumber()));
2275 						position.next();
2276 						break;
2277 					}
2278 					ExtensionFunction extensionFunction = callTuple.getExtensionFunctions().get(awkName);
2279 					if (extensionFunction != null) {
2280 						resolveIndirectArguments(actualArguments, extensionFunction);
2281 						if (profiling) {
2282 							activeProfilingFunctions.push(new ActiveFunction(awkName, tupleStartNanos));
2283 						}
2284 						try {
2285 							push(
2286 									invokeExtension(
2287 											extensionFunction,
2288 											actualArguments,
2289 											position.lineNumber(),
2290 											true));
2291 						} finally {
2292 							if (profiling) {
2293 								recordFunctionExit(System.nanoTime());
2294 							}
2295 						}
2296 						position.next();
2297 						break;
2298 					}
2299 					throw new AwkRuntimeException(
2300 							position.lineNumber(),
2301 							"function `" + qualifiedName + "' is not defined");
2302 				}
2303 				case FUNCTION: {
2304 					// important for compilation,
2305 					// not needed for interpretation
2306 					// arg[0] = function name
2307 					// arg[1] = # of formal parameters
2308 					position.next();
2309 					break;
2310 				}
2311 				case WARNING: {
2312 					jrt.printWarning(((Tuple.WarningTuple) tuple).getMessage());
2313 					position.next();
2314 					break;
2315 				}
2316 				case SET_RETURN_RESULT: {
2317 					// stack[0] = return result
2318 					runtimeStack.setReturnValue(pop());
2319 					position.next();
2320 					break;
2321 				}
2322 				case RETURN_FROM_FUNCTION: {
2323 					releaseElementArgumentReferences();
2324 					position.jump(runtimeStack.popFrame());
2325 					push(runtimeStack.getReturnValue());
2326 					position.next();
2327 					break;
2328 				}
2329 				case SET_NUM_GLOBALS: {
2330 					execSetNumGlobals((CountTuple) tuple);
2331 					position.next();
2332 					break;
2333 				}
2334 				case BEFORE_START_HOOKS: {
2335 					runBeforeStartHooks();
2336 					position.next();
2337 					break;
2338 				}
2339 				case UPDATE_SYMTAB: {
2340 					execUpdateSymtab(((LongTuple) tuple).getValue());
2341 					position.next();
2342 					break;
2343 				}
2344 				case UPDATE_FUNCTAB: {
2345 					execUpdateFunctab(((LongTuple) tuple).getValue());
2346 					position.next();
2347 					break;
2348 				}
2349 				case CLOSE: {
2350 					// stack[0] = file or command line to close
2351 					String s = jrt.toAwkString(pop());
2352 					push(jrt.jrtClose(s));
2353 					position.next();
2354 					break;
2355 				}
2356 				case APPLY_SUBSEP: {
2357 					execApplySubsep((CountTuple) tuple);
2358 					position.next();
2359 					break;
2360 				}
2361 				case DELETE_ARRAY_ELEMENT: {
2362 					// arg[0] = offset
2363 					// arg[1] = isGlobal
2364 					// stack[0] = array index
2365 					VariableTuple variableTuple = (VariableTuple) tuple;
2366 					long offset = variableTuple.getVariableOffset();
2367 					boolean isGlobal = variableTuple.isGlobal();
2368 					Map<Object, Object> aa = getMapVariable(offset, isGlobal);
2369 					Object key = pop();
2370 					checkScalar(key);
2371 					if (aa != null) {
2372 						aa.remove(key);
2373 						detachMissingArrayArgumentReferences(aa);
2374 					}
2375 					position.next();
2376 					break;
2377 				}
2378 				case DELETE_MAP_ELEMENT: {
2379 					// stack[0] = array index
2380 					// stack[1] = associative array
2381 					Object key = pop();
2382 					checkScalar(key);
2383 					Map<Object, Object> aa = toMap(pop());
2384 					aa.remove(key);
2385 					detachMissingArrayArgumentReferences(aa);
2386 					position.next();
2387 					break;
2388 				}
2389 				case DELETE_ARRAY: {
2390 					// arg[0] = offset
2391 					// arg[1] = isGlobal
2392 					// (nothing on the stack)
2393 					VariableTuple variableTuple = (VariableTuple) tuple;
2394 					long offset = variableTuple.getVariableOffset();
2395 					boolean isGlobal = variableTuple.isGlobal();
2396 					Map<Object, Object> array = getMapVariable(offset, isGlobal);
2397 					if (array != null) {
2398 						array.clear();
2399 						detachMissingArrayArgumentReferences(array);
2400 					}
2401 					position.next();
2402 					break;
2403 				}
2404 				case SET_WITHIN_END_BLOCKS: {
2405 					// arg[0] = whether within the END blocks section
2406 					BooleanTuple endBlocksTuple = (BooleanTuple) tuple;
2407 					withinEndBlocks = endBlocksTuple.getValue();
2408 					position.next();
2409 					break;
2410 				}
2411 				case EXIT_WITHOUT_CODE:
2412 				case EXIT_WITH_CODE: {
2413 					if (opcode == Opcode.EXIT_WITH_CODE) {
2414 						// stack[0] = exit code
2415 						exitCode = (int) JRT.toDouble(pop());
2416 					}
2417 					throwExitException = true;
2418 					withinBeginFileBlocks = false;
2419 					withinEndFileBlocks = false;
2420 
2421 					// If in BEGIN or in a rule, jump to the END section
2422 					if (!withinEndBlocks && exitAddress != null) {
2423 						resetCallState();
2424 						position.jump(exitAddress);
2425 					} else {
2426 						// Exit immediately with ExitException
2427 						// clear operand stack
2428 						operandStack.clear();
2429 						throw new ExitException(exitCode, "The AWK script requested an exit");
2430 						// position.next();
2431 					}
2432 					break;
2433 				}
2434 				case REGEXP: {
2435 					// Literal regex tuples must provide a precompiled Pattern as arg[1]
2436 					RegexTuple regexTuple = (RegexTuple) tuple;
2437 					Pattern pattern = regexTuple.getPattern();
2438 					push(pattern);
2439 					position.next();
2440 					break;
2441 				}
2442 				case CONDITION_PAIR: {
2443 					// Legacy opcode kept for precompiled tuple streams
2444 					// stack[0] = End condition
2445 					// stack[1] = Start condition
2446 					if (conditionPairs == null) {
2447 						conditionPairs = new HashMap<Long, ConditionPair>();
2448 					}
2449 					long currentIndex = position.currentIndex();
2450 					ConditionPair cp = conditionPairs.get(currentIndex);
2451 					if (cp == null) {
2452 						cp = new ConditionPair();
2453 						conditionPairs.put(currentIndex, cp);
2454 					}
2455 					boolean end = jrt.toBoolean(pop());
2456 					boolean start = jrt.toBoolean(pop());
2457 					push(cp.update(start, end) ? ONE : ZERO);
2458 					position.next();
2459 					break;
2460 				}
2461 				case CONDITION_PAIR_IN_RANGE: {
2462 					// arg[0] = unique identifier of the range pattern
2463 					if (conditionPairs == null) {
2464 						conditionPairs = new HashMap<Long, ConditionPair>();
2465 					}
2466 					long id = ((LongTuple) tuple).getValue();
2467 					ConditionPair cp = conditionPairs.get(id);
2468 					if (cp == null) {
2469 						cp = new ConditionPair();
2470 						conditionPairs.put(id, cp);
2471 					}
2472 					push(cp.isWithin() ? ONE : ZERO);
2473 					position.next();
2474 					break;
2475 				}
2476 				case CONDITION_PAIR_ENTER: {
2477 					// arg[0] = unique identifier of the range pattern
2478 					// A CONDITION_PAIR_IN_RANGE tuple for the same identifier always
2479 					// executes first, so the pair is already registered
2480 					conditionPairs.get(((LongTuple) tuple).getValue()).enter();
2481 					position.next();
2482 					break;
2483 				}
2484 				case CONDITION_PAIR_LEAVE: {
2485 					// arg[0] = unique identifier of the range pattern
2486 					conditionPairs.get(((LongTuple) tuple).getValue()).leave();
2487 					position.next();
2488 					break;
2489 				}
2490 				case IS_IN: {
2491 					// stack[1] = key to check
2492 					Object arr = pop();
2493 					Object arg = pop();
2494 					checkScalar(arg);
2495 					if (isUntyped(arr)) {
2496 						push(ZERO);
2497 						position.next();
2498 						break;
2499 					}
2500 					if (!(arr instanceof Map)) {
2501 						throw new AwkRuntimeException("Attempting to use a scalar as an array.");
2502 					}
2503 					@SuppressWarnings("unchecked")
2504 					Map<Object, Object> aa = (Map<Object, Object>) arr;
2505 					boolean result = JRT.containsAwkKey(aa, arg);
2506 					push(result ? ONE : ZERO);
2507 					position.next();
2508 					break;
2509 				}
2510 				case THIS: {
2511 					// this is in preparation for a function
2512 					// call for the JVM-COMPILED script, only
2513 					// therefore, do NOTHING for the interpreted
2514 					// version
2515 					position.next();
2516 					break;
2517 				}
2518 				case EXTENSION: {
2519 					// arg[0] = extension function metadata
2520 					// arg[1] = # of args on the stack
2521 					// arg[2] = true if parent is NOT an extension function call
2522 					// (i.e., initial extension in calling expression)
2523 					// stack[0] = first actual parameter
2524 					// stack[1] = second actual parameter
2525 					// etc.
2526 					ExtensionTuple extensionTuple = (ExtensionTuple) tuple;
2527 					ExtensionFunction function = extensionTuple.getFunction();
2528 					long numArgs = extensionTuple.getArgCount();
2529 					boolean isInitial = extensionTuple.isInitial();
2530 					push(
2531 							invokeExtension(
2532 									function,
2533 									popArguments(numArgs),
2534 									position.lineNumber(),
2535 									isInitial));
2536 
2537 					position.next();
2538 					break;
2539 				}
2540 				case ASSIGN_NF: {
2541 					Object v = pop();
2542 					jrt.setNF(v);
2543 					push(v);
2544 					position.next();
2545 					break;
2546 				}
2547 				case PUSH_NF: {
2548 					push(jrt.getNF());
2549 					position.next();
2550 					break;
2551 				}
2552 				case ASSIGN_NR: {
2553 					Object v = pop();
2554 					jrt.setNR(v);
2555 					push(v);
2556 					position.next();
2557 					break;
2558 				}
2559 				case PUSH_NR: {
2560 					push(jrt.getNR());
2561 					position.next();
2562 					break;
2563 				}
2564 				case ASSIGN_FNR: {
2565 					Object v = pop();
2566 					jrt.setFNR(v);
2567 					push(v);
2568 					position.next();
2569 					break;
2570 				}
2571 				case PUSH_FNR: {
2572 					push(jrt.getFNR());
2573 					position.next();
2574 					break;
2575 				}
2576 				case ASSIGN_FS: {
2577 					Object v = pop();
2578 					jrt.setFS(v);
2579 					push(v);
2580 					position.next();
2581 					break;
2582 				}
2583 				case ASSIGN_IGNORECASE: {
2584 					Object v = pop();
2585 					jrt.setIGNORECASE(v);
2586 					push(v);
2587 					position.next();
2588 					break;
2589 				}
2590 				case PUSH_IGNORECASE: {
2591 					push(jrt.getIGNORECASEVar());
2592 					position.next();
2593 					break;
2594 				}
2595 				case PUSH_FS: {
2596 					push(jrt.getFSVar());
2597 					position.next();
2598 					break;
2599 				}
2600 				case ASSIGN_RS: {
2601 					Object v = pop();
2602 					jrt.setRS(v);
2603 					push(v);
2604 					position.next();
2605 					break;
2606 				}
2607 				case PUSH_RS: {
2608 					push(jrt.getRSVar());
2609 					position.next();
2610 					break;
2611 				}
2612 				case ASSIGN_OFS: {
2613 					Object v = pop();
2614 					jrt.setOFS(v);
2615 					push(v);
2616 					position.next();
2617 					break;
2618 				}
2619 				case PUSH_OFS: {
2620 					push(jrt.getOFSVar());
2621 					position.next();
2622 					break;
2623 				}
2624 				case ASSIGN_ORS: {
2625 					Object v = pop();
2626 					jrt.setORS(v);
2627 					push(v);
2628 					position.next();
2629 					break;
2630 				}
2631 				case PUSH_ORS: {
2632 					push(jrt.getORSVar());
2633 					position.next();
2634 					break;
2635 				}
2636 				case ASSIGN_RSTART: {
2637 					Object v = pop();
2638 					jrt.setRSTART(v);
2639 					push(v);
2640 					position.next();
2641 					break;
2642 				}
2643 				case PUSH_RSTART: {
2644 					push(jrt.getRSTART());
2645 					position.next();
2646 					break;
2647 				}
2648 				case ASSIGN_RLENGTH: {
2649 					Object v = pop();
2650 					jrt.setRLENGTH(v);
2651 					push(v);
2652 					position.next();
2653 					break;
2654 				}
2655 				case PUSH_RLENGTH: {
2656 					push(jrt.getRLENGTH());
2657 					position.next();
2658 					break;
2659 				}
2660 				case ASSIGN_FILENAME: {
2661 					Object v = pop();
2662 					jrt.setFILENAMEViaJrt(v);
2663 					push(v == null ? "" : v);
2664 					position.next();
2665 					break;
2666 				}
2667 				case PUSH_FILENAME: {
2668 					push(jrt.getFILENAME());
2669 					position.next();
2670 					break;
2671 				}
2672 				case ASSIGN_ERRNO: {
2673 					Object v = pop();
2674 					jrt.setERRNO(v);
2675 					push(v == null ? "" : v);
2676 					position.next();
2677 					break;
2678 				}
2679 				case PUSH_ERRNO: {
2680 					push(jrt.getERRNO());
2681 					position.next();
2682 					break;
2683 				}
2684 				case ASSIGN_ARGIND: {
2685 					Object v = pop();
2686 					jrt.setARGIND(v);
2687 					push(v == null ? ZERO : v);
2688 					position.next();
2689 					break;
2690 				}
2691 				case PUSH_ARGIND: {
2692 					push(jrt.getARGIND());
2693 					position.next();
2694 					break;
2695 				}
2696 				case ASSIGN_SUBSEP: {
2697 					Object v = pop();
2698 					jrt.setSUBSEP(v);
2699 					push(v);
2700 					position.next();
2701 					break;
2702 				}
2703 				case PUSH_SUBSEP: {
2704 					push(jrt.getSUBSEPVar());
2705 					position.next();
2706 					break;
2707 				}
2708 				case ASSIGN_CONVFMT: {
2709 					Object v = pop();
2710 					jrt.setCONVFMT(v);
2711 					push(v);
2712 					position.next();
2713 					break;
2714 				}
2715 				case PUSH_CONVFMT: {
2716 					push(jrt.getCONVFMTVar());
2717 					position.next();
2718 					break;
2719 				}
2720 				case ASSIGN_OFMT: {
2721 					Object v = pop();
2722 					jrt.setOFMT(v);
2723 					push(v);
2724 					position.next();
2725 					break;
2726 				}
2727 				case PUSH_OFMT: {
2728 					push(getOFMT());
2729 					position.next();
2730 					break;
2731 				}
2732 				case ASSIGN_ARGC: {
2733 					Object v = pop();
2734 					if (argcOffset == NULL_OFFSET) {
2735 						throw new AwkRuntimeException("ARGC is read-only (not materialized).");
2736 					}
2737 					runtimeStack.setVariable(argcOffset, v, true);
2738 					push(v);
2739 					position.next();
2740 					break;
2741 				}
2742 				case PUSH_ARGC: {
2743 					if (argcOffset == NULL_OFFSET) {
2744 						push(getARGC());
2745 					} else {
2746 						push(runtimeStack.getVariable(argcOffset, true));
2747 					}
2748 					position.next();
2749 					break;
2750 				}
2751 				default:
2752 					throw new Error("invalid opcode: " + position.opcode());
2753 				}
2754 				if (profiling) {
2755 					afterProfiledTuple(opcode, tupleStartNanos);
2756 				}
2757 			}
2758 
2759 		} catch (ExitException ee) {
2760 			if (profiling && (opcode == Opcode.EXIT_WITH_CODE || opcode == Opcode.EXIT_WITHOUT_CODE)) {
2761 				afterProfiledTuple(opcode, tupleStartNanos);
2762 			}
2763 			throw ee;
2764 		} catch (IOException ioe) {
2765 			resetCallState();
2766 			throw ioe;
2767 		} catch (RuntimeException re) {
2768 			resetCallState();
2769 			if (re instanceof AwkSandboxException) {
2770 				throw re;
2771 			}
2772 			throw new AwkRuntimeException(position.lineNumber(), re.getMessage(), re);
2773 		} catch (AssertionError ae) {
2774 			resetCallState();
2775 			throw ae;
2776 		}
2777 
2778 		// If <code>exit</code> was called, throw an ExitException
2779 		if (throwExitException) {
2780 			throw new ExitException(exitCode, "The AWK script requested an exit");
2781 		}
2782 	}
2783 
2784 	/**
2785 	 * Clears all collected profiling statistics.
2786 	 */
2787 	public void resetProfiling() {
2788 		if (!profiling) {
2789 			return;
2790 		}
2791 		tupleProfilingStats.clear();
2792 		functionProfilingStats.clear();
2793 		activeProfilingFunctions.clear();
2794 	}
2795 
2796 	/**
2797 	 * Returns an immutable snapshot of the collected profiling statistics.
2798 	 *
2799 	 * @return profiling report snapshot
2800 	 */
2801 	public ProfilingReport getProfilingReport() {
2802 		if (!profiling) {
2803 			return ProfilingReport.empty();
2804 		}
2805 		return new ProfilingReport(tupleProfilingStats, functionProfilingStats);
2806 	}
2807 
2808 	private void execPrint(CountTuple tuple) throws IOException {
2809 		long numArgs = tuple.getCount();
2810 		jrt.printDefault(numArgs == 0 ? new Object[] { jrt.jrtGetInputField(0) } : popArguments(numArgs));
2811 	}
2812 
2813 	private void execPrintToFile(CountAndAppendTuple tuple) throws IOException {
2814 		String key = jrt.toAwkString(pop());
2815 		long numArgs = tuple.getCount();
2816 		jrt
2817 				.printToFile(
2818 						key,
2819 						tuple.isAppend(),
2820 						numArgs == 0 ? new Object[]
2821 						{ jrt.jrtGetInputField(0) } : popArguments(numArgs));
2822 	}
2823 
2824 	private void execPrintToPipe(CountTuple tuple) throws IOException {
2825 		String cmd = jrt.toAwkString(pop());
2826 		long numArgs = tuple.getCount();
2827 		jrt.printToProcess(cmd, numArgs == 0 ? new Object[] { jrt.jrtGetInputField(0) } : popArguments(numArgs));
2828 	}
2829 
2830 	private void execPrintf(CountTuple tuple) throws IOException {
2831 		long numArgs = tuple.getCount();
2832 		Object[] values = popArguments(numArgs - 1);
2833 		String format = jrt.toAwkString(pop());
2834 		jrt.printfDefault(format, values);
2835 	}
2836 
2837 	private void execPrintfToFile(CountAndAppendTuple tuple) throws IOException {
2838 		String key = jrt.toAwkString(pop());
2839 		long numArgs = tuple.getCount();
2840 		Object[] values = popArguments(numArgs - 1);
2841 		String format = jrt.toAwkString(pop());
2842 		jrt.printfToFile(key, tuple.isAppend(), format, values);
2843 	}
2844 
2845 	private void execPrintfToPipe(CountTuple tuple) throws IOException {
2846 		String cmd = jrt.toAwkString(pop());
2847 		long numArgs = tuple.getCount();
2848 		Object[] values = popArguments(numArgs - 1);
2849 		String format = jrt.toAwkString(pop());
2850 		jrt.printfToProcess(cmd, format, values);
2851 	}
2852 
2853 	private void execLength(CountTuple tuple) {
2854 		long num = tuple.getCount();
2855 		if (num == 0) {
2856 			push(jrt.jrtGetInputField(0).toString().length());
2857 			return;
2858 		}
2859 		Object value = pop();
2860 		if (value instanceof ArgumentReference) {
2861 			value = resolveLengthArgumentReference((ArgumentReference) value);
2862 		}
2863 		push(lengthOf(value));
2864 	}
2865 
2866 	private Object lengthOf(Object value) {
2867 		return value instanceof Map ?
2868 				Long.valueOf(((Map<?, ?>) value).size()) : Integer.valueOf(jrt.toAwkString(value).length());
2869 	}
2870 
2871 	private String normalizeIndirectFunctionName(String functionName) {
2872 		return functionName.startsWith("awk::") ? functionName.substring("awk::".length()) : functionName;
2873 	}
2874 
2875 	/**
2876 	 * Unwinds every call frame and clears the per-call runtime state, after an
2877 	 * {@code exit} statement or an abandoned execution.
2878 	 */
2879 	private void resetCallState() {
2880 		runtimeStack.popAllFrames();
2881 		elementArgumentReferences.clear();
2882 		operandStack.clear();
2883 	}
2884 
2885 	/**
2886 	 * Registers the untyped element references received by the call frame that
2887 	 * was just pushed, so array mutations occurring during the call can detach
2888 	 * them. References forwarded from an enclosing call keep their original
2889 	 * owner.
2890 	 */
2891 	private void adoptElementArgumentReferences(Object[] actualArguments) {
2892 		for (Object argument : actualArguments) {
2893 			adoptElementArgumentReference(argument);
2894 		}
2895 	}
2896 
2897 	private void adoptElementArgumentReference(Object argument) {
2898 		if (argument instanceof IndirectArrayArgumentReference) {
2899 			IndirectArrayArgumentReference reference = (IndirectArrayArgumentReference) argument;
2900 			if (reference.ownerFrame < 0) {
2901 				reference.ownerFrame = runtimeStack.frameCount();
2902 				elementArgumentReferences.push(reference);
2903 			}
2904 		}
2905 	}
2906 
2907 	/**
2908 	 * Drops the element references owned by the call frame that is about to be
2909 	 * popped. Frames are strictly nested, so the owned references always sit on
2910 	 * top of the tracking stack.
2911 	 */
2912 	private void releaseElementArgumentReferences() {
2913 		int depth = runtimeStack.frameCount();
2914 		while (!elementArgumentReferences.isEmpty()
2915 				&& elementArgumentReferences.peek().ownerFrame == depth) {
2916 			elementArgumentReferences.pop();
2917 		}
2918 	}
2919 
2920 	private Object resolveUserFunctionArgument(Object argument) {
2921 		if (!(argument instanceof ArgumentReference)) {
2922 			return argument;
2923 		}
2924 		ArgumentReference reference = (ArgumentReference) argument;
2925 		Object snapshot = reference.snapshot();
2926 		if (snapshot instanceof ArgumentReference) {
2927 			return resolveUserFunctionArgument(snapshot);
2928 		}
2929 		return isUntyped(snapshot) ? reference : snapshot;
2930 	}
2931 
2932 	private void resolveIndirectArguments(
2933 			Object[] actualArgumentsParam,
2934 			BuiltinFunction builtin) {
2935 		for (int index = 0; index < actualArgumentsParam.length; index++) {
2936 			boolean arrayArgument = builtin == BuiltinFunction.SPLIT && index == 1;
2937 			actualArgumentsParam[index] = resolveIndirectArgument(
2938 					actualArgumentsParam[index],
2939 					arrayArgument,
2940 					false);
2941 		}
2942 	}
2943 
2944 	private void resolveIndirectArguments(
2945 			Object[] actualArgumentsParam,
2946 			ExtensionFunction function) {
2947 		boolean[] arrayArguments = new boolean[actualArgumentsParam.length];
2948 		boolean[] rawValueArguments = new boolean[actualArgumentsParam.length];
2949 		for (int index : function.collectAssocArrayIndexes(actualArgumentsParam.length)) {
2950 			arrayArguments[index] = true;
2951 		}
2952 		for (int index : function.collectRawValueIndexes(actualArgumentsParam.length)) {
2953 			rawValueArguments[index] = true;
2954 		}
2955 		for (int index = 0; index < actualArgumentsParam.length; index++) {
2956 			actualArgumentsParam[index] = resolveIndirectArgument(
2957 					actualArgumentsParam[index],
2958 					arrayArguments[index],
2959 					rawValueArguments[index]);
2960 		}
2961 	}
2962 
2963 	private Object resolveIndirectArgument(
2964 			Object argument,
2965 			boolean arrayArgument,
2966 			boolean rawValueArgument) {
2967 		if (!(argument instanceof ArgumentReference)) {
2968 			return argument;
2969 		}
2970 		ArgumentReference reference = (ArgumentReference) argument;
2971 		if (rawValueArgument) {
2972 			return resolveRawArgumentReference(reference);
2973 		}
2974 		return resolveArgumentReference(reference, arrayArgument);
2975 	}
2976 
2977 	private Object invokeIndirectBuiltin(
2978 			BuiltinFunction builtin,
2979 			Object[] args,
2980 			int lineNumber) {
2981 		switch (builtin) {
2982 		case ATAN2:
2983 			requireIndirectArgumentCount(builtin, args, 2, 2, lineNumber);
2984 			return Math.atan2(JRT.toDouble(args[0]), JRT.toDouble(args[1]));
2985 		case CLOSE:
2986 			requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
2987 			return jrt.jrtClose(jrt.toAwkString(args[0]));
2988 		case COS:
2989 			requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
2990 			return Math.cos(JRT.toDouble(args[0]));
2991 		case EXP:
2992 			requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
2993 			return Math.exp(JRT.toDouble(args[0]));
2994 		case GSUB:
2995 		case SUB:
2996 			requireIndirectArgumentCount(builtin, args, 2, 2, lineNumber);
2997 			return substituteInputLine(
2998 					builtin == BuiltinFunction.GSUB,
2999 					args[0],
3000 					args[1]);
3001 		case INDEX:
3002 			requireIndirectArgumentCount(builtin, args, 2, 2, lineNumber);
3003 			return jrt.index(jrt.toAwkString(args[0]), jrt.toAwkString(args[1]));
3004 		case INT:
3005 			requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3006 			return Long.valueOf((long) JRT.toDouble(args[0]));
3007 		case LENGTH:
3008 			requireIndirectArgumentCount(builtin, args, 0, 1, lineNumber);
3009 			return args.length == 0 ? Integer.valueOf(jrt.jrtGetInputField(0).toString().length()) : lengthOf(args[0]);
3010 		case LOG:
3011 			requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3012 			return Math.log(JRT.toDouble(args[0]));
3013 		case MATCH:
3014 			requireIndirectArgumentCount(builtin, args, 2, 2, lineNumber);
3015 			return jrt.matchPosition(jrt.toAwkString(args[0]), jrt.toAwkString(args[1]));
3016 		case RAND:
3017 			requireIndirectArgumentCount(builtin, args, 0, 0, lineNumber);
3018 			return randomNumberGenerator.nextDouble();
3019 		case SIN:
3020 			requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3021 			return Math.sin(JRT.toDouble(args[0]));
3022 		case SPLIT:
3023 			requireIndirectArgumentCount(builtin, args, 2, 3, lineNumber);
3024 			return splitIntoArray(
3025 					args[0],
3026 					args[1],
3027 					args.length == 3 ? args[2] : jrt.getFSVar(),
3028 					lineNumber);
3029 		case SPRINTF:
3030 			requireIndirectArgumentCount(builtin, args, 1, Integer.MAX_VALUE, lineNumber);
3031 			return jrt
3032 					.getAwkSink()
3033 					.sprintf(
3034 							jrt.toAwkString(args[0]),
3035 							Arrays.copyOfRange(args, 1, args.length));
3036 		case SQRT:
3037 			requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3038 			return Math.sqrt(JRT.toDouble(args[0]));
3039 		case SRAND:
3040 			requireIndirectArgumentCount(builtin, args, 0, 1, lineNumber);
3041 			int seed = args.length == 0 ? JRT.timeSeed() : (int) JRT.toDouble(args[0]);
3042 			int previousSeed = randomNumberGenerator.getSeed();
3043 			randomNumberGenerator.setSeed(seed);
3044 			return Integer.valueOf(previousSeed);
3045 		case SUBSTR:
3046 			requireIndirectArgumentCount(builtin, args, 2, 3, lineNumber);
3047 			return substring(
3048 					args[0],
3049 					args[1],
3050 					args.length == 3 ? args[2] : null);
3051 		case SYSTEM:
3052 			requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3053 			return jrt.jrtSystem(jrt.toAwkString(args[0]));
3054 		case TOLOWER:
3055 			requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3056 			return jrt.toAwkString(args[0]).toLowerCase();
3057 		case TOUPPER:
3058 			requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3059 			return jrt.toAwkString(args[0]).toUpperCase();
3060 		default:
3061 			throw new AwkRuntimeException(
3062 					lineNumber,
3063 					"indirect calls are not implemented for builtin `" + builtin.getAwkName() + "'");
3064 		}
3065 	}
3066 
3067 	private void requireIndirectArgumentCount(
3068 			BuiltinFunction builtin,
3069 			Object[] args,
3070 			int minimum,
3071 			int maximum,
3072 			int lineNumber) {
3073 		if (args.length < minimum || args.length > maximum) {
3074 			String expected = minimum == maximum ? Integer.toString(minimum) : minimum + " to " + maximum;
3075 			throw new AwkRuntimeException(
3076 					lineNumber,
3077 					builtin.getAwkName() + " requires " + expected + " argument(s), not " + args.length);
3078 		}
3079 	}
3080 
3081 	private Object invokeExtension(
3082 			ExtensionFunction function,
3083 			Object[] args,
3084 			int lineNumber,
3085 			boolean blockResult) {
3086 		// Let extensions report diagnostics at the call location.
3087 		currentLineNumber = lineNumber;
3088 		String extensionClassName = function.getExtensionClassName();
3089 		JawkExtension extension = extensionInstances.get(extensionClassName);
3090 		if (extension == null) {
3091 			throw new AwkRuntimeException(
3092 					lineNumber,
3093 					"Extension instance for class '" + extensionClassName + "' is not registered");
3094 		}
3095 		if (!(extension instanceof AbstractExtension)) {
3096 			throw new AwkRuntimeException(
3097 					lineNumber,
3098 					"Extension instance for class '" + extensionClassName
3099 							+ "' does not extend "
3100 							+ AbstractExtension.class.getName());
3101 		}
3102 		Map<IndirectArrayArgumentReference, Object> attachedValues = captureAttachedArrayArgumentValues();
3103 		Object result;
3104 		try {
3105 			result = function.invoke((AbstractExtension) extension, args);
3106 		} finally {
3107 			detachReplacedArrayArgumentReferences(attachedValues);
3108 		}
3109 		if (blockResult && result instanceof BlockObject) {
3110 			result = new BlockManager().block((BlockObject) result);
3111 		}
3112 		if (result == null) {
3113 			return "";
3114 		}
3115 		if (result instanceof Number
3116 				|| result instanceof String
3117 				|| result instanceof Map
3118 				|| result instanceof BlockObject) {
3119 			return result;
3120 		}
3121 		return jrt.toAwkString(result);
3122 	}
3123 
3124 	private void execMatch() {
3125 		String ere = jrt.toAwkString(pop());
3126 		String s = jrt.toAwkString(pop());
3127 		push(jrt.matchPosition(s, ere));
3128 	}
3129 
3130 	private void execSubForDollar0(BooleanTuple tuple) {
3131 		Object replacement = pop();
3132 		Object ere = pop();
3133 		push(substituteInputLine(tuple.getValue(), ere, replacement));
3134 	}
3135 
3136 	private Object substituteInputLine(boolean global, Object ere, Object replacement) {
3137 		String orig = jrt.toAwkString(jrt.jrtGetInputField(0));
3138 		Object replacements = global ?
3139 				jrt.replaceAll(orig, jrt.toAwkString(replacement), jrt.toAwkString(ere)) :
3140 				jrt.replaceFirst(orig, jrt.toAwkString(replacement), jrt.toAwkString(ere));
3141 		jrt.setInputLine(jrt.getReplaceResult());
3142 		jrt.jrtParseFields();
3143 		return replacements;
3144 	}
3145 
3146 	private void execSubForDollarReference(BooleanTuple tuple) {
3147 		boolean isGsub = tuple.getValue();
3148 		long fieldNum = JRT.parseFieldNumber(pop());
3149 		String orig = jrt.toAwkString(pop());
3150 		String repl = jrt.toAwkString(pop());
3151 		String ere = jrt.toAwkString(pop());
3152 		push(isGsub ? jrt.replaceAll(orig, repl, ere) : jrt.replaceFirst(orig, repl, ere));
3153 		String newstring = jrt.getReplaceResult();
3154 		if (fieldNum == 0) {
3155 			jrt.setInputLine(newstring);
3156 			jrt.jrtParseFields();
3157 		} else {
3158 			jrt.jrtSetInputField(newstring, fieldNum);
3159 		}
3160 	}
3161 
3162 	private void execSubForVariable(SubstitutionVariableTuple tuple, PositionTracker position) {
3163 		String newString = execSubOrGSub(tuple.isGlobalSubstitution());
3164 		assign(tuple.getVariableOffset(), newString, tuple.isGlobal(), position, false);
3165 	}
3166 
3167 	private void execSubForArrayReference(SubstitutionVariableTuple tuple) {
3168 		Object arrIdx = pop();
3169 		String newString = execSubOrGSub(tuple.isGlobalSubstitution());
3170 		assignArray(tuple.getVariableOffset(), arrIdx, newString, tuple.isGlobal());
3171 		pop();
3172 	}
3173 
3174 	private void execSubForMapReference(BooleanTuple tuple) {
3175 		Object arrIdx = pop();
3176 		Map<Object, Object> array = toMap(pop());
3177 		String newString = execSubOrGSub(tuple.getValue());
3178 		assignMapElement(array, arrIdx, newString);
3179 		pop();
3180 	}
3181 
3182 	private void execSplit(CountTuple tuple, PositionTracker position) {
3183 		long numArgs = tuple.getCount();
3184 		Object fs;
3185 		if (numArgs == 2) {
3186 			fs = jrt.getFSVar();
3187 		} else if (numArgs == 3) {
3188 			// a regexp literal arrives precompiled and stays that way, so the
3189 			// tokenizer can honor IGNORECASE through the pattern-twin cache
3190 			fs = pop();
3191 		} else {
3192 			throw new Error("Invalid # of args. split() requires 2 or 3. Got: " + numArgs);
3193 		}
3194 		Object target = pop();
3195 		Object source = pop();
3196 		push(splitIntoArray(source, target, fs, position.lineNumber()));
3197 	}
3198 
3199 	private Object splitIntoArray(Object source, Object target, Object separator, int lineNumber) {
3200 		if (!(target instanceof Map)) {
3201 			throw new AwkRuntimeException(lineNumber, target + " is not an array.");
3202 		}
3203 		Enumeration<Object> tokenizer = jrt.splitTokenizer(jrt.toAwkString(source), separator);
3204 		@SuppressWarnings("unchecked")
3205 		Map<Object, Object> assocArray = (Map<Object, Object>) target;
3206 		assocArray.clear();
3207 		detachMissingArrayArgumentReferences(assocArray);
3208 		long cnt = 0;
3209 		while (tokenizer.hasMoreElements()) {
3210 			Object value = tokenizer.nextElement();
3211 			assocArray.put(++cnt, jrt.toInputScalar(value));
3212 		}
3213 		return Long.valueOf(cnt);
3214 	}
3215 
3216 	private void execSubstr(CountTuple tuple) {
3217 		long numArgs = tuple.getCount();
3218 		Object length = null;
3219 		if (numArgs == 3) {
3220 			length = pop();
3221 		} else if (numArgs != 2) {
3222 			throw new Error("numArgs for SUBSTR must be 2 or 3. It is " + numArgs);
3223 		}
3224 		Object start = pop();
3225 		Object value = pop();
3226 		push(substring(value, start, length));
3227 	}
3228 
3229 	private Object substring(Object value, Object start, Object requestedLength) {
3230 		String s = jrt.toAwkString(value);
3231 		int startPos = (int) JRT.toDouble(start);
3232 		int length = requestedLength == null ?
3233 				s.length() - startPos + 1 : (int) JRT.toLong(requestedLength);
3234 		if (startPos <= 0) {
3235 			startPos = 1;
3236 		}
3237 		if (length <= 0 || startPos > s.length()) {
3238 			return BLANK;
3239 		}
3240 		return startPos + length > s.length() ?
3241 				s.substring(startPos - 1) : s.substring(startPos - 1, startPos + length - 1);
3242 	}
3243 
3244 	private void execSetNumGlobals(CountTuple tuple) {
3245 		long numGlobals = tuple.getCount();
3246 		Object[] globals = runtimeStack.getNumGlobals();
3247 		if (mergedGlobalLayoutActive) {
3248 			if (!hasCompatiblePersistentGlobalLayout(numGlobals)) {
3249 				throw new IllegalStateException(
3250 						"AVM globals are already initialized for an incompatible persistent layout.");
3251 			}
3252 			applyExecutionInitialVariablesToGlobalSlots(true);
3253 		} else if (globals == null) {
3254 			runtimeStack.setNumGlobals(numGlobals, globalVariableOffsets);
3255 			initializedEvalGlobalVariableOffsets = globalVariableOffsets;
3256 			initializedEvalGlobalVariableArrays = globalVariableArrays;
3257 			applyExecutionInitialVariablesToGlobalSlots(false);
3258 		} else if (!hasCompatibleEvalGlobalLayout(numGlobals)) {
3259 			throw new IllegalStateException(
3260 					"AVM globals are already initialized for a different eval layout. Call prepareForEval(...) first.");
3261 		}
3262 	}
3263 
3264 	private void populateEnviron(long offset) {
3265 		environOffset = offset;
3266 		for (Map.Entry<String, String> var : System.getenv().entrySet()) {
3267 			assignArray(environOffset, var.getKey(), jrt.toInputScalar(var.getValue()), true);
3268 			pop(); // clean up the stack after the assignment
3269 		}
3270 	}
3271 
3272 	private void populateArgc(long offset) {
3273 		argcOffset = offset;
3274 		// +1 to include the "jawk" program name (ARGV[0])
3275 		runtimeStack.setVariable(argcOffset, Integer.valueOf(arguments.size() + 1), true);
3276 	}
3277 
3278 	private void populateArgv(long offset) {
3279 		argvOffset = offset;
3280 		// A host-supplied ARGV takes precedence over the operand list: leave
3281 		// it untouched instead of overwriting its entries.
3282 		Object existing = runtimeStack.getVariable(argvOffset, true);
3283 		if (existing instanceof Map && !((Map<?, ?>) existing).isEmpty()) {
3284 			return;
3285 		}
3286 		forEachArgvEntry((index, value) -> {
3287 			assignArray(argvOffset, index, value, true);
3288 			pop(); // clean up the stack after the assignment
3289 		});
3290 	}
3291 
3292 	/**
3293 	 * Supplies the ARGV entries to the given consumer, in index order:
3294 	 * ARGV[0] is the program name, ARGV[1..n] the command-line arguments.
3295 	 * Indexes are supplied as {@code Long}, the canonical AWK array key
3296 	 * form. The count comes straight from the argument list because ARGC
3297 	 * may not be materialized when the script does not reference it.
3298 	 *
3299 	 * @param consumer receives each (index, value) ARGV entry
3300 	 */
3301 	private void forEachArgvEntry(BiConsumer<Long, Object> consumer) {
3302 		consumer.accept(Long.valueOf(0L), "jawk");
3303 		for (int i = 1; i <= arguments.size(); i++) {
3304 			consumer.accept(Long.valueOf(i), jrt.toInputScalar(arguments.get(i - 1)));
3305 		}
3306 	}
3307 
3308 	/*
3309 	 * Extension initialization can be heavy, so the hooks run at most once per
3310 	 * AVM instance: a reused AVM (repeated executions, expression evaluations)
3311 	 * does not reinitialize its extensions.
3312 	 */
3313 	private void runBeforeStartHooks() {
3314 		if (beforeStartHooksExecuted || extensionInstances.isEmpty()) {
3315 			return;
3316 		}
3317 		beforeStartHooksExecuted = true;
3318 		Set<JawkExtension> started = new LinkedHashSet<JawkExtension>();
3319 		for (JawkExtension extension : extensionInstances.values()) {
3320 			if (started.add(extension)) {
3321 				extension.beforeStart(this, jrt);
3322 			}
3323 		}
3324 	}
3325 
3326 	/*
3327 	 * SYMTAB is a gawk extension mirroring the symbol table: the script's
3328 	 * globals, the JRT-managed specials, and host-supplied variables that have
3329 	 * no compiled slot. The parser emits UPDATE_SYMTAB only when the script
3330 	 * references SYMTAB outside POSIX mode. Declared globals and managed special
3331 	 * variables remain linked to their runtime values.
3332 	 */
3333 	private void execUpdateSymtab(long offset) {
3334 		symtabOffset = offset;
3335 		if (runtimeStack.getVariable(offset, true) != null) {
3336 			// a host-supplied SYMTAB value wins
3337 			return;
3338 		}
3339 		SymtabArray symtab = new SymtabArray();
3340 		for (String name : executionInitialVariables.keySet()) {
3341 			symtab.put(name, getVariable(name));
3342 		}
3343 		for (String name : getGlobalVariableNames()) {
3344 			// gawk keeps the meta tables out of the symbol table snapshot
3345 			if ("SYMTAB".equals(name) || "FUNCTAB".equals(name)) {
3346 				continue;
3347 			}
3348 			symtab.put(name, getVariable(name));
3349 		}
3350 		// specials last: their accessors are authoritative even when the name
3351 		// also has a (possibly not yet materialized) global slot
3352 		for (String name : getSpecialVariableNames()) {
3353 			symtab.put(name, getVariable(name));
3354 		}
3355 		symtab.activate();
3356 		runtimeStack.setVariable(offset, symtab, true);
3357 	}
3358 
3359 	private final class SymtabArray extends java.util.AbstractMap<Object, Object> implements AssocArray {
3360 		private final Map<Object, Object> entries = newAwkArray();
3361 		private final Set<String> assignableNames = new HashSet<String>();
3362 		private boolean active;
3363 
3364 		private void activate() {
3365 			active = true;
3366 		}
3367 
3368 		/** {@inheritDoc} */
3369 		@Override
3370 		public Object get(Object key) {
3371 			String name = key == null ? "" : key.toString();
3372 			if (active && !isMetaTableName(name) && isLiveSpecialVariable(name)) {
3373 				return getVariable(name);
3374 			}
3375 			Integer offset = globalVariableOffsets == null ? null : globalVariableOffsets.get(name);
3376 			if (active && !isMetaTableName(name) && offset != null) {
3377 				return runtimeStack.getVariable(offset.intValue(), true);
3378 			}
3379 			return entries.get(key);
3380 		}
3381 
3382 		/** {@inheritDoc} */
3383 		@Override
3384 		public boolean containsKey(Object key) {
3385 			String name = key == null ? "" : key.toString();
3386 			return active
3387 					&& !isMetaTableName(name)
3388 					&& (isLiveSpecialVariable(name)
3389 							|| (globalVariableOffsets != null && globalVariableOffsets.containsKey(name)))
3390 					|| entries.containsKey(key);
3391 		}
3392 
3393 		/** {@inheritDoc} */
3394 		@Override
3395 		public Object put(Object key, Object value) {
3396 			String name = key == null ? "" : key.toString();
3397 			if (!active) {
3398 				if (key != null) {
3399 					assignableNames.add(name);
3400 				}
3401 				return entries.put(key, value);
3402 			}
3403 			if (!assignableNames.contains(name)) {
3404 				throw new AwkRuntimeException("Cannot assign to an arbitrary element of SYMTAB.");
3405 			}
3406 			validateGlobalType(name, value);
3407 			Object previous = entries.put(key, value);
3408 			if (isMetaTableName(name)) {
3409 				return previous;
3410 			}
3411 			if (applyLiveSpecialVariable(name, value)) {
3412 				return previous;
3413 			}
3414 			Integer offset = globalVariableOffsets == null ? null : globalVariableOffsets.get(name);
3415 			if (offset != null) {
3416 				runtimeStack.setVariable(offset.intValue(), value, true);
3417 			}
3418 			return previous;
3419 		}
3420 
3421 		private Object putRuntimeVariable(String name, Object value) {
3422 			assignableNames.add(name);
3423 			return put(name, value);
3424 		}
3425 
3426 		private boolean applyLiveSpecialVariable(String name, Object value) {
3427 			if ("ARGC".equals(name)) {
3428 				if (argcOffset == NULL_OFFSET) {
3429 					throw new AwkRuntimeException("ARGC is read-only (not materialized).");
3430 				}
3431 				runtimeStack.setVariable(argcOffset, value, true);
3432 				return true;
3433 			}
3434 			if ("RSTART".equals(name)) {
3435 				jrt.setRSTART(value);
3436 				return true;
3437 			}
3438 			if ("RLENGTH".equals(name)) {
3439 				jrt.setRLENGTH(value);
3440 				return true;
3441 			}
3442 			return isManagedSpecialVariable(name) && jrt.applySpecialVariable(name, value);
3443 		}
3444 
3445 		private boolean isLiveSpecialVariable(String name) {
3446 			return isManagedSpecialVariable(name)
3447 					|| "RSTART".equals(name)
3448 					|| "RLENGTH".equals(name);
3449 		}
3450 
3451 		/** {@inheritDoc} */
3452 		@Override
3453 		public Object remove(Object key) {
3454 			if (active) {
3455 				throw new AwkRuntimeException("Cannot delete an element from SYMTAB.");
3456 			}
3457 			return entries.remove(key);
3458 		}
3459 
3460 		/** {@inheritDoc} */
3461 		@Override
3462 		public void clear() {
3463 			if (active) {
3464 				throw new AwkRuntimeException("Cannot delete SYMTAB.");
3465 			}
3466 			entries.clear();
3467 		}
3468 
3469 		private void validateGlobalType(String name, Object value) {
3470 			Boolean array = globalVariableArrays == null ? null : globalVariableArrays.get(name);
3471 			if (Boolean.TRUE.equals(array) && !(value instanceof Map)) {
3472 				throw new AwkRuntimeException(
3473 						"Attempting to use array `" + name + "' in a scalar context.");
3474 			}
3475 			if (Boolean.FALSE.equals(array) && value instanceof Map) {
3476 				throw new AwkRuntimeException(
3477 						"Attempting to use scalar `" + name + "' as an array.");
3478 			}
3479 		}
3480 
3481 		/** {@inheritDoc} */
3482 		@Override
3483 		public Set<Map.Entry<Object, Object>> entrySet() {
3484 			return new AbstractSet<Map.Entry<Object, Object>>() {
3485 
3486 				@Override
3487 				public Iterator<Map.Entry<Object, Object>> iterator() {
3488 					final Iterator<Map.Entry<Object, Object>> iterator = entries.entrySet().iterator();
3489 					return new Iterator<Map.Entry<Object, Object>>() {
3490 
3491 						@Override
3492 						public boolean hasNext() {
3493 							return iterator.hasNext();
3494 						}
3495 
3496 						@Override
3497 						public Map.Entry<Object, Object> next() {
3498 							return new LiveSymtabEntry(iterator.next().getKey());
3499 						}
3500 
3501 						@Override
3502 						public void remove() {
3503 							if (active) {
3504 								throw new AwkRuntimeException("Cannot delete an element from SYMTAB.");
3505 							}
3506 							iterator.remove();
3507 						}
3508 					};
3509 				}
3510 
3511 				@Override
3512 				public int size() {
3513 					return entries.size();
3514 				}
3515 			};
3516 		}
3517 
3518 		private boolean isMetaTableName(String name) {
3519 			return "SYMTAB".equals(name) || "FUNCTAB".equals(name);
3520 		}
3521 
3522 		private final class LiveSymtabEntry implements Map.Entry<Object, Object> {
3523 
3524 			private final Object key;
3525 
3526 			private LiveSymtabEntry(Object key) {
3527 				this.key = key;
3528 			}
3529 
3530 			@Override
3531 			public Object getKey() {
3532 				return key;
3533 			}
3534 
3535 			@Override
3536 			public Object getValue() {
3537 				return SymtabArray.this.get(key);
3538 			}
3539 
3540 			@Override
3541 			public Object setValue(Object value) {
3542 				return SymtabArray.this.put(key, value);
3543 			}
3544 
3545 			@Override
3546 			public boolean equals(Object obj) {
3547 				if (!(obj instanceof Map.Entry)) {
3548 					return false;
3549 				}
3550 				Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
3551 				return Objects.equals(key, other.getKey()) && Objects.equals(getValue(), other.getValue());
3552 			}
3553 
3554 			@Override
3555 			public int hashCode() {
3556 				return Objects.hashCode(key) ^ Objects.hashCode(getValue());
3557 			}
3558 		}
3559 	}
3560 
3561 	/*
3562 	 * FUNCTAB is a gawk extension listing the names of the standard built-in
3563 	 * functions, the program's user-defined functions, and the loaded
3564 	 * extensions' function keywords. The parser emits UPDATE_FUNCTAB only when
3565 	 * the script references FUNCTAB outside POSIX mode.
3566 	 */
3567 	private void execUpdateFunctab(long offset) {
3568 		if (runtimeStack.getVariable(offset, true) != null) {
3569 			return;
3570 		}
3571 		Map<Object, Object> functab = newAwkArray();
3572 		for (String name : BuiltinFunction.names()) {
3573 			functab.put(name, name);
3574 		}
3575 		for (String name : functionNames) {
3576 			functab.put(name, name);
3577 		}
3578 		Set<JawkExtension> seen = new LinkedHashSet<JawkExtension>();
3579 		for (JawkExtension extension : extensionInstances.values()) {
3580 			if (seen.add(extension)) {
3581 				for (String keyword : extension.getExtensionFunctions().keySet()) {
3582 					functab.put(keyword, keyword);
3583 				}
3584 			}
3585 		}
3586 		runtimeStack.setVariable(offset, new ReadOnlyArray("FUNCTAB", functab), true);
3587 	}
3588 
3589 	private static final class ReadOnlyArray extends java.util.AbstractMap<Object, Object> implements AssocArray {
3590 		private final String name;
3591 		private final Map<Object, Object> entries;
3592 
3593 		private ReadOnlyArray(String nameParam, Map<Object, Object> entriesParam) {
3594 			name = nameParam;
3595 			entries = entriesParam;
3596 		}
3597 
3598 		/** {@inheritDoc} */
3599 		@Override
3600 		public Object get(Object key) {
3601 			return JRT.containsAwkKey(entries, key) ? entries.get(key) : BLANK;
3602 		}
3603 
3604 		/** {@inheritDoc} */
3605 		@Override
3606 		public boolean containsKey(Object key) {
3607 			return JRT.containsAwkKey(entries, key);
3608 		}
3609 
3610 		/** {@inheritDoc} */
3611 		@Override
3612 		public Set<Map.Entry<Object, Object>> entrySet() {
3613 			return Collections.unmodifiableMap(entries).entrySet();
3614 		}
3615 
3616 		/** {@inheritDoc} */
3617 		@Override
3618 		public Object put(Object key, Object value) {
3619 			throw readOnlyError();
3620 		}
3621 
3622 		/** {@inheritDoc} */
3623 		@Override
3624 		public Object remove(Object key) {
3625 			throw readOnlyError();
3626 		}
3627 
3628 		/** {@inheritDoc} */
3629 		@Override
3630 		public void clear() {
3631 			throw readOnlyError();
3632 		}
3633 
3634 		private AwkRuntimeException readOnlyError() {
3635 			return new AwkRuntimeException(name + " is read-only.");
3636 		}
3637 	}
3638 
3639 	/** Reflects a command-line variable assignment into a materialized SYMTAB. */
3640 	private void updateSymtabEntry(String name, Object value) {
3641 		if (symtabOffset == NULL_OFFSET) {
3642 			return;
3643 		}
3644 		Object symtab = runtimeStack.getVariable(symtabOffset, true);
3645 		if (symtab instanceof SymtabArray) {
3646 			((SymtabArray) symtab).putRuntimeVariable(name, value);
3647 		} else if (symtab instanceof Map) {
3648 			@SuppressWarnings("unchecked")
3649 			Map<Object, Object> symtabMap = (Map<Object, Object>) symtab;
3650 			symtabMap.put(name, value);
3651 		}
3652 	}
3653 
3654 	private void execApplySubsep(CountTuple tuple) {
3655 		long count = tuple.getCount();
3656 		if (count == 1) {
3657 			Object value = pop();
3658 			checkScalar(value);
3659 			push(jrt.toAwkString(value));
3660 			return;
3661 		}
3662 		StringBuilder sb = new StringBuilder();
3663 		Object value = pop();
3664 		checkScalar(value);
3665 		sb.append(jrt.toAwkString(value));
3666 		String subsep = jrt.toAwkString(jrt.getSUBSEPVar());
3667 		for (int i = 1; i < count; i++) {
3668 			sb.insert(0, subsep);
3669 			value = pop();
3670 			checkScalar(value);
3671 			sb.insert(0, jrt.toAwkString(value));
3672 		}
3673 		push(sb.toString());
3674 	}
3675 
3676 	private long beforeProfiledTuple(Tuple tuple, Opcode opcode) {
3677 		long now = System.nanoTime();
3678 		if (opcode == Opcode.CALL_FUNCTION) {
3679 			CallFunctionTuple callTuple = (CallFunctionTuple) tuple;
3680 			activeProfilingFunctions.push(new ActiveFunction(callTuple.getFunctionName(), now));
3681 		} else if (opcode == Opcode.EXTENSION) {
3682 			ExtensionTuple extensionTuple = (ExtensionTuple) tuple;
3683 			ExtensionFunction function = extensionTuple.getFunction();
3684 			activeProfilingFunctions.push(new ActiveFunction(function.getKeyword(), now));
3685 		}
3686 		return now;
3687 	}
3688 
3689 	private void afterProfiledTuple(Opcode opcode, long tupleStartNanos) {
3690 		long now = System.nanoTime();
3691 		statisticsFor(tupleProfilingStats, opcode).add(now - tupleStartNanos);
3692 		if (opcode == Opcode.EXIT_WITH_CODE || opcode == Opcode.EXIT_WITHOUT_CODE) {
3693 			recordAllFunctionExits(now);
3694 		} else if (opcode == Opcode.EXTENSION || opcode == Opcode.RETURN_FROM_FUNCTION) {
3695 			recordFunctionExit(now);
3696 		}
3697 	}
3698 
3699 	private static <K> ProfilingReport.Accumulator statisticsFor(
3700 			Map<K, ProfilingReport.Accumulator> stats,
3701 			K key) {
3702 		ProfilingReport.Accumulator accumulator = stats.get(key);
3703 		if (accumulator == null) {
3704 			accumulator = new ProfilingReport.Accumulator();
3705 			stats.put(key, accumulator);
3706 		}
3707 		return accumulator;
3708 	}
3709 
3710 	private void recordFunctionExit(long now) {
3711 		if (activeProfilingFunctions.isEmpty()) {
3712 			return;
3713 		}
3714 		ActiveFunction function = activeProfilingFunctions.pop();
3715 		statisticsFor(functionProfilingStats, function.name).add(now - function.startNanos);
3716 	}
3717 
3718 	private void recordAllFunctionExits(long now) {
3719 		while (!activeProfilingFunctions.isEmpty()) {
3720 			recordFunctionExit(now);
3721 		}
3722 	}
3723 
3724 	private static final class ActiveFunction {
3725 		private final String name;
3726 		private final long startNanos;
3727 
3728 		private ActiveFunction(String name, long startNanos) {
3729 			this.name = name;
3730 			this.startNanos = startNanos;
3731 		}
3732 	}
3733 
3734 	/**
3735 	 * Releases any prepared input source and runtime I/O resources owned by this
3736 	 * AVM.
3737 	 * <p>
3738 	 * Call this when you are done with an AVM obtained through expert-level
3739 	 * integration, or after direct {@link #eval(AwkExpression, InputSource)} /
3740 	 * {@link #execute(AwkProgram, InputSource)} usage.
3741 	 * The AVM may be prepared again afterwards, but callers should treat a closed
3742 	 * instance as end-of-use unless they intentionally reinitialize it.
3743 	 * </p>
3744 	 */
3745 	@Override
3746 	public void close() throws IOException {
3747 		jrt.jrtCloseAll();
3748 		closeResolvedInputSource();
3749 		resolvedInputSource = null;
3750 	}
3751 
3752 	/**
3753 	 * Close the resolved {@link InputSource} if it implements {@link Closeable}.
3754 	 * This is used by {@link #close()} and by explicit rebind operations such as
3755 	 * {@link #prepareForEval(InputSource)} when the AVM switches to a different
3756 	 * source instance.
3757 	 */
3758 	private void closeResolvedInputSource() {
3759 		closeInputSource(resolvedInputSource);
3760 	}
3761 
3762 	private void closeInputSource(InputSource inputSource) {
3763 		if (!(inputSource instanceof Closeable)) {
3764 			return;
3765 		}
3766 		try {
3767 			((Closeable) inputSource).close();
3768 		} catch (IOException ignored) {
3769 			// Best-effort close.
3770 		}
3771 	}
3772 
3773 	private Object[] popArguments(long numArgs) {
3774 		Object[] args = new Object[(int) numArgs];
3775 		for (int i = (int) numArgs - 1; i >= 0; i--) {
3776 			args[i] = pop();
3777 		}
3778 		return args;
3779 	}
3780 
3781 	/**
3782 	 * sprintf() functionality
3783 	 */
3784 	private String sprintfFunction(long numArgs) {
3785 		Object[] argArray = popArguments(numArgs - 1);
3786 		String fmt = jrt.toAwkString(pop());
3787 		return jrt.getAwkSink().sprintf(fmt, argArray);
3788 	}
3789 
3790 	private void setNumOnJRT(long fieldNum, double num) {
3791 		String numString = jrt.toAwkString(Double.valueOf(num));
3792 
3793 		// same code as ASSIGN_AS_INPUT_FIELD
3794 		if (fieldNum == 0) {
3795 			jrt.setInputLine(numString);
3796 			jrt.jrtParseFields();
3797 		} else {
3798 			jrt.jrtSetInputField(numString, fieldNum);
3799 		}
3800 	}
3801 
3802 	private String execSubOrGSub(boolean isGsub) {
3803 		String newString;
3804 
3805 		// stack[0] = original field value
3806 		// stack[1] = replacement string
3807 		// stack[2] = ere
3808 		String orig = jrt.toAwkString(pop());
3809 		String repl = jrt.toAwkString(pop());
3810 		String ere = jrt.toAwkString(pop());
3811 		push(isGsub ? jrt.replaceAll(orig, repl, ere) : jrt.replaceFirst(orig, repl, ere));
3812 		newString = jrt.getReplaceResult();
3813 
3814 		return newString;
3815 	}
3816 
3817 	/**
3818 	 * Awk variable assignment functionality.
3819 	 */
3820 	private void assign(long l, Object value, boolean isGlobal, PositionTracker position, boolean push) {
3821 		value = JRT.untypedToBlank(value);
3822 		checkScalar(value);
3823 		// check if curr value already refers to an array
3824 		if (resolveVariable(l, isGlobal, false) instanceof Map) {
3825 			throw new AwkRuntimeException(position.lineNumber(), "Attempting to use an array in a scalar context.");
3826 		}
3827 		if (push) {
3828 			push(value);
3829 		}
3830 		runtimeStack.setVariable(l, value, isGlobal);
3831 		// When specials are compiled correctly, they use ASSIGN_* and skip this path.
3832 	}
3833 
3834 	/**
3835 	 * Awk array element assignment functionality.
3836 	 */
3837 	private void assignArray(long offset, Object arrIdx, Object rhs, boolean isGlobal) {
3838 		assignMapElement(ensureMapVariable(offset, isGlobal), arrIdx, rhs);
3839 	}
3840 
3841 	private void assignMapElement(Map<Object, Object> array, Object arrIdx, Object rhs) {
3842 		checkScalar(arrIdx);
3843 		rhs = JRT.untypedToBlank(rhs);
3844 		checkScalar(rhs);
3845 		if (JRT.containsAwkKey(array, arrIdx)
3846 				&& JRT.getAssocArrayValue(array, arrIdx) instanceof Map) {
3847 			throw new AwkRuntimeException("Attempting to use an array in a scalar context.");
3848 		}
3849 		array.put(arrIdx, rhs);
3850 		push(rhs);
3851 	}
3852 
3853 	/**
3854 	 * Numerically increases an Awk variable by one; the result
3855 	 * is placed back into that variable.
3856 	 */
3857 	private Object inc(long l, boolean isGlobal) {
3858 		Object o = resolveVariable(l, isGlobal, false);
3859 		if (o instanceof UninitializedObject) {
3860 			o = ZERO;
3861 			runtimeStack.setVariable(l, o, isGlobal);
3862 		}
3863 		Object updated = JRT.inc(o);
3864 		runtimeStack.setVariable(l, updated, isGlobal);
3865 		return o;
3866 	}
3867 
3868 	/**
3869 	 * Numerically decreases an Awk variable by one; the result
3870 	 * is placed back into that variable.
3871 	 */
3872 	private Object dec(long l, boolean isGlobal) {
3873 		Object o = resolveVariable(l, isGlobal, false);
3874 		if (o instanceof UninitializedObject) {
3875 			o = ZERO;
3876 			runtimeStack.setVariable(l, o, isGlobal);
3877 		}
3878 		Object updated = JRT.dec(o);
3879 		runtimeStack.setVariable(l, updated, isGlobal);
3880 		return o;
3881 	}
3882 
3883 	/** {@inheritDoc} */
3884 	@Override
3885 	public final Object getRS() {
3886 		return jrt.getRSVar();
3887 	}
3888 
3889 	/** {@inheritDoc} */
3890 	@Override
3891 	public final Object getOFS() {
3892 		return jrt.getOFSVar();
3893 	}
3894 
3895 	/** {@inheritDoc} */
3896 	@Override
3897 	public final Object getORS() {
3898 		return jrt.getORSVar();
3899 	}
3900 
3901 	/** {@inheritDoc} */
3902 	@Override
3903 	public final Object getSUBSEP() {
3904 		return jrt.getSUBSEPVar();
3905 	}
3906 
3907 	/**
3908 	 * Returns the names of the global variables declared by the compiled
3909 	 * program.
3910 	 *
3911 	 * @return unmodifiable set of global variable names, empty when no program
3912 	 *         metadata is installed
3913 	 */
3914 	public Set<String> getGlobalVariableNames() {
3915 		return globalVariableOffsets == null ?
3916 				Collections.<String>emptySet() : Collections.unmodifiableSet(globalVariableOffsets.keySet());
3917 	}
3918 
3919 	/**
3920 	 * Returns the names of the user-defined functions of the compiled program.
3921 	 *
3922 	 * @return unmodifiable set of function names, empty when no program metadata
3923 	 *         is installed
3924 	 */
3925 	public Set<String> getFunctionNames() {
3926 		return functionNames == null ? Collections.<String>emptySet() : Collections.unmodifiableSet(functionNames);
3927 	}
3928 
3929 	/**
3930 	 * The names of the special variables the interpreter answers by name.
3931 	 * Must stay in sync with the switch in {@link #getVariable(String)}; a
3932 	 * unit test verifies that every listed name is answered.
3933 	 */
3934 	private static final Set<String> SPECIAL_VARIABLE_NAMES = Collections
3935 			.unmodifiableSet(
3936 					new LinkedHashSet<String>(
3937 							Arrays
3938 									.asList(
3939 											"FS",
3940 											"RS",
3941 											"OFS",
3942 											"ORS",
3943 											"FILENAME",
3944 											"SUBSEP",
3945 											"CONVFMT",
3946 											"OFMT",
3947 											"NF",
3948 											"NR",
3949 											"FNR",
3950 											"RSTART",
3951 											"RLENGTH",
3952 											"IGNORECASE",
3953 											"ERRNO",
3954 											"ARGIND",
3955 											"ARGC",
3956 											"ARGV")));
3957 
3958 	/**
3959 	 * Returns the names of the special variables that
3960 	 * {@link #getVariable(String)} answers directly.
3961 	 *
3962 	 * @return unmodifiable set of special variable names
3963 	 */
3964 	public Set<String> getSpecialVariableNames() {
3965 		return SPECIAL_VARIABLE_NAMES;
3966 	}
3967 
3968 	/** {@inheritDoc} */
3969 	@Override
3970 	public final Object getVariable(String name) {
3971 		if (name == null) {
3972 			return null;
3973 		}
3974 		switch (name) {
3975 		case "FS":
3976 			return getFS();
3977 		case "RS":
3978 			return getRS();
3979 		case "OFS":
3980 			return getOFS();
3981 		case "ORS":
3982 			return getORS();
3983 		case "FILENAME":
3984 			return jrt.getFILENAME();
3985 		case "SUBSEP":
3986 			return getSUBSEP();
3987 		case "CONVFMT":
3988 			return getCONVFMT();
3989 		case "OFMT":
3990 			return jrt.getOFMTString();
3991 		case "NF":
3992 			return jrt.getNF();
3993 		case "NR":
3994 			return jrt.getNR();
3995 		case "FNR":
3996 			return jrt.getFNR();
3997 		case "RSTART":
3998 			return jrt.getRSTART();
3999 		case "RLENGTH":
4000 			return jrt.getRLENGTH();
4001 		case "IGNORECASE":
4002 			return jrt.getIGNORECASEVar();
4003 		case "ERRNO":
4004 			if (isManagedSpecialVariable(name)) {
4005 				return jrt.getERRNO();
4006 			}
4007 			// POSIX mode: ordinary global, answered by the slot lookup below
4008 			break;
4009 		case "ARGIND":
4010 			if (isManagedSpecialVariable(name)) {
4011 				return jrt.getARGIND();
4012 			}
4013 			// POSIX mode: ordinary global, answered by the slot lookup below
4014 			break;
4015 		// lazily-materialized globals answered through their synthetic accessors
4016 		case "ARGC":
4017 			return getARGC();
4018 		case "ARGV":
4019 			return getARGV();
4020 		default:
4021 			break;
4022 		}
4023 		if (globalVariableOffsets == null) {
4024 			return executionInitialVariables.get(name);
4025 		}
4026 		Integer offsetObj = globalVariableOffsets.get(name);
4027 		if (offsetObj != null) {
4028 			return runtimeStack.getVariable(offsetObj.intValue(), true);
4029 		}
4030 		// Variables supplied through -v or the Java API but never referenced in
4031 		// the script have no compiled offset; they are still observable (e.g.
4032 		// IGNORECASE read by the gawk extension).
4033 		return executionInitialVariables == null ? null : executionInitialVariables.get(name);
4034 	}
4035 
4036 	/**
4037 	 * Returns the description of the primary script source (typically its file
4038 	 * name), for extension-emitted diagnostics.
4039 	 *
4040 	 * @return script source description, or {@code null} when unknown
4041 	 */
4042 	public String getSourceDescription() {
4043 		return sourceDescription;
4044 	}
4045 
4046 	/**
4047 	 * Returns the script line of the extension call currently being dispatched,
4048 	 * for extension-emitted diagnostics.
4049 	 *
4050 	 * @return current script line number
4051 	 */
4052 	public int getCurrentLineNumber() {
4053 		return currentLineNumber;
4054 	}
4055 
4056 	/** {@inheritDoc} */
4057 	@Override
4058 	public final void assignVariable(String name, Object obj) {
4059 		// When offsets are not available yet, treat the assignment as part of this
4060 		// AVM's baseline initial-variable snapshot.
4061 		if (globalVariableOffsets == null || globalVariableArrays == null) {
4062 			Object normalized = normalizeExternalVariableValue(obj);
4063 			baseInitialVariables.put(name, normalized);
4064 			if (isManagedSpecialVariable(name)) {
4065 				baseSpecialVariables.put(name, normalized);
4066 			}
4067 			return;
4068 		}
4069 
4070 		// make sure we're not receiving funcname=value assignments
4071 		if (functionNames.contains(name)) {
4072 			throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + name + ").");
4073 		}
4074 
4075 		Object normalized = normalizeExternalVariableValue(obj);
4076 		// Runtime assignments to JRT-managed specials (e.g. IGNORECASE=1 or
4077 		// FS=: between input files) must reach the JRT, not a global slot.
4078 		// ARGC is excluded: JRT.setARGC delegates back to this method, and its
4079 		// authoritative storage is the compiled slot below.
4080 		if (!"ARGC".equals(name) && isManagedSpecialVariable(name)) {
4081 			jrt.applySpecialVariable(name, normalized);
4082 			updateSymtabEntry(name, normalized);
4083 			return;
4084 		}
4085 
4086 		Integer offsetObj = globalVariableOffsets.get(name);
4087 		Boolean arrayObj = globalVariableArrays.get(name);
4088 
4089 		if (offsetObj != null) {
4090 			if (arrayObj.booleanValue() && !(normalized instanceof Map)) {
4091 				throw new IllegalArgumentException(
4092 						"Cannot assign a scalar to a non-scalar variable (" + name + ").");
4093 			}
4094 			runtimeStack.setFilelistVariable(offsetObj.intValue(), normalized);
4095 		} else if (runtimeStack.hasGlobalVariable(name)) {
4096 			runtimeStack.setGlobalVariable(name, normalized);
4097 		}
4098 		// names without a compiled slot are still symbols: keep SYMTAB current
4099 		updateSymtabEntry(name, normalized);
4100 	}
4101 
4102 	/**
4103 	 * Executes the {@code nextfile} statement: abandons the current input file
4104 	 * and resumes the per-file input loop at the appropriate point. The
4105 	 * runtime and operand stacks are cleared, so {@code nextfile} unwinds
4106 	 * user-defined function calls, mirroring {@code exit}.
4107 	 * <p>
4108 	 * A {@code nextfile} written directly inside a BEGIN, END, or ENDFILE
4109 	 * rule is already rejected at compile time by the parser. The checks
4110 	 * below cover the uses reached through user-defined functions, where the
4111 	 * calling rule cannot be known statically (the same function may be
4112 	 * called from both an ordinary rule and an END rule); gawk performs the
4113 	 * same checks at runtime.
4114 	 * </p>
4115 	 *
4116 	 * @param position the tuple position tracker to redirect
4117 	 */
4118 	private void executeNextfile(PositionTracker position) {
4119 		if (nextFileAddress == null || !inputFileLoopStarted) {
4120 			throw new AwkRuntimeException(
4121 					position.lineNumber(),
4122 					"`nextfile' cannot be called from a BEGIN rule");
4123 		}
4124 		if (withinEndBlocks) {
4125 			throw new AwkRuntimeException(
4126 					position.lineNumber(),
4127 					"`nextfile' cannot be called from an END rule");
4128 		}
4129 		if (withinEndFileBlocks) {
4130 			throw new AwkRuntimeException(
4131 					position.lineNumber(),
4132 					"`nextfile' cannot be called from an ENDFILE rule");
4133 		}
4134 		// nextfile can be invoked from user-defined functions: unwind them.
4135 		runtimeStack.popAllFrames();
4136 		operandStack.clear();
4137 		if (endFileAddress == null
4138 				|| withinBeginFileBlocks && jrt.hasPendingInputFileError(resolvedInputSource)) {
4139 			// No ENDFILE rules to run, or the file could not be opened: skip
4140 			// the ENDFILE section (gawk BEGINFILE error handling) and go
4141 			// straight to the next file.
4142 			withinBeginFileBlocks = false;
4143 			position.jump(nextFileAddress);
4144 		} else {
4145 			withinBeginFileBlocks = false;
4146 			withinEndFileBlocks = true;
4147 			position.jump(endFileAddress);
4148 		}
4149 	}
4150 
4151 	/**
4152 	 * Returns whether a non-redirected {@code getline} must be confined to
4153 	 * the current input file. While the per-file main input loop of a program
4154 	 * with BEGINFILE/ENDFILE rules is running, only the loop itself may cross
4155 	 * file boundaries, so that no file's hooks are ever skipped; a
4156 	 * {@code getline} in an action therefore reports end-of-input at the end
4157 	 * of the current file. In BEGIN and END rules — before the loop starts or
4158 	 * after it ends — {@code getline} keeps streaming across the remaining
4159 	 * input.
4160 	 *
4161 	 * @return {@code true} when getline must not advance to the next file
4162 	 */
4163 	private boolean isMainInputFileBounded() {
4164 		return endFileAddress != null && inputFileLoopStarted && !withinEndBlocks;
4165 	}
4166 
4167 	/**
4168 	 * Raises the gawk-compatible fatal error when a non-redirected
4169 	 * {@code getline} executes inside a BEGINFILE or ENDFILE rule.
4170 	 * <p>
4171 	 * A non-redirected {@code getline} written directly inside a
4172 	 * BEGINFILE/ENDFILE rule is already rejected at compile time by the
4173 	 * parser. This runtime check covers the uses reached through
4174 	 * user-defined functions, where the calling rule cannot be known
4175 	 * statically; it lives here rather than in {@link JRT} because the
4176 	 * current-rule flags are interpreter execution state.
4177 	 * </p>
4178 	 *
4179 	 * @param position the tuple position tracker, for error reporting
4180 	 */
4181 	private void checkGetlineAllowed(PositionTracker position) {
4182 		if (withinBeginFileBlocks || withinEndFileBlocks) {
4183 			throw new AwkRuntimeException(
4184 					position.lineNumber(),
4185 					"non-redirected `getline' invalid inside `"
4186 							+ (withinBeginFileBlocks ? "BEGINFILE" : "ENDFILE")
4187 							+ "' rule");
4188 		}
4189 	}
4190 
4191 	/** {@inheritDoc} */
4192 	@Override
4193 	public Object getFS() {
4194 		return jrt.getFSVar();
4195 	}
4196 
4197 	/** {@inheritDoc} */
4198 	@Override
4199 	public Object getCONVFMT() {
4200 		return jrt.getCONVFMTString();
4201 	}
4202 
4203 	/** {@inheritDoc} */
4204 	@Override
4205 	public void resetFNR() {
4206 		jrt.setFNR(0);
4207 	}
4208 
4209 	/** {@inheritDoc} */
4210 	@Override
4211 	public void incFNR() {
4212 		long v = jrt.getFNR();
4213 		jrt.setFNR(v + 1);
4214 	}
4215 
4216 	/** {@inheritDoc} */
4217 	@Override
4218 	public void incNR() {
4219 		long v = jrt.getNR();
4220 		jrt.setNR(v + 1);
4221 	}
4222 
4223 	/** {@inheritDoc} */
4224 	@Override
4225 	public void setNF(Integer newNf) {
4226 		jrt.setNF(newNf);
4227 	}
4228 
4229 	/** {@inheritDoc} */
4230 	@Override
4231 	public void setFILENAME(String filename) {
4232 		jrt.setFILENAMEViaJrt(jrt.toInputScalar(filename));
4233 	}
4234 
4235 	/** {@inheritDoc} */
4236 	@Override
4237 	public Object getARGV() {
4238 		if (argvOffset == NULL_OFFSET) {
4239 			Map<Object, Object> argv = newAwkArray();
4240 			forEachArgvEntry(argv::put);
4241 			return argv;
4242 		}
4243 		return runtimeStack.getVariable(argvOffset, true);
4244 	}
4245 
4246 	/** {@inheritDoc} */
4247 	@Override
4248 	public Object getARGC() {
4249 		if (argcOffset == NULL_OFFSET) {
4250 			return Long.valueOf(arguments.size() + 1);
4251 		}
4252 		return runtimeStack.getVariable(argcOffset, true);
4253 	}
4254 
4255 	private String getOFMT() {
4256 		return jrt.getOFMTString();
4257 	}
4258 
4259 	private Map<Object, Object> newAwkArray() {
4260 		return JRT.createAwkMap(sortedArrayKeys);
4261 	}
4262 
4263 	private Map<Object, Object> ensureMapVariable(long offset, boolean isGlobal) {
4264 		return toMap(resolveVariable(offset, isGlobal, true));
4265 	}
4266 
4267 	private Map<Object, Object> getMapVariable(long offset, boolean isGlobal) {
4268 		return toMap(resolveVariable(offset, isGlobal, true));
4269 	}
4270 
4271 	private Object resolveVariable(long offset, boolean isGlobal, boolean arrayContext) {
4272 		Object value = runtimeStack.getVariable(offset, isGlobal);
4273 		// Argument references are only ever stored in local parameter slots,
4274 		// so global reads skip the reference check entirely.
4275 		if (!isGlobal && value instanceof ArgumentReference) {
4276 			value = resolveArgumentReference((ArgumentReference) value, arrayContext);
4277 			runtimeStack.setVariable(offset, value, isGlobal);
4278 			return value;
4279 		}
4280 		if (!isUntyped(value)) {
4281 			return value;
4282 		}
4283 		value = arrayContext ? newAwkArray() : BLANK;
4284 		runtimeStack.setVariable(offset, value, isGlobal);
4285 		return value;
4286 	}
4287 
4288 	private Object resolveRawArgumentReference(ArgumentReference reference) {
4289 		Object currentValue = readCurrentArgumentValue(reference);
4290 		if (currentValue instanceof Map
4291 				|| currentValue instanceof UninitializedObject
4292 						&& !(currentValue instanceof UntypedObject)) {
4293 			return currentValue;
4294 		}
4295 		Object value = reference.snapshot();
4296 		return value instanceof ArgumentReference ?
4297 				resolveRawArgumentReference((ArgumentReference) value) : value;
4298 	}
4299 
4300 	private Object readCurrentArgumentValue(ArgumentReference reference) {
4301 		Object value = reference.currentValue();
4302 		return value instanceof ArgumentReference ?
4303 				readCurrentArgumentValue((ArgumentReference) value) : value;
4304 	}
4305 
4306 	private Object resolveArgumentReference(ArgumentReference reference, boolean arrayContext) {
4307 		if (!arrayContext) {
4308 			checkScalar(readCurrentArgumentValue(reference));
4309 		}
4310 		Object value = arrayContext ? reference.currentValue() : reference.snapshot();
4311 		if (value instanceof ArgumentReference) {
4312 			value = resolveArgumentReference((ArgumentReference) value, arrayContext);
4313 			if (arrayContext) {
4314 				reference.setValue(value);
4315 			} else {
4316 				reference.setScalarValue(value);
4317 			}
4318 			return value;
4319 		}
4320 		if (!isUntyped(value)) {
4321 			return value;
4322 		}
4323 		value = arrayContext ? newAwkArray() : BLANK;
4324 		if (arrayContext) {
4325 			reference.setValue(value);
4326 		} else {
4327 			reference.setScalarValue(value);
4328 		}
4329 		return value;
4330 	}
4331 
4332 	private Object resolveLengthArgumentReference(ArgumentReference reference) {
4333 		Object currentValue = readCurrentArgumentValue(reference);
4334 		return currentValue instanceof Map ? currentValue : resolveArgumentReference(reference, false);
4335 	}
4336 
4337 	private Map<IndirectArrayArgumentReference, Object> captureAttachedArrayArgumentValues() {
4338 		if (elementArgumentReferences.isEmpty()) {
4339 			return Collections.emptyMap();
4340 		}
4341 		Map<IndirectArrayArgumentReference, Object> values = new IdentityHashMap<IndirectArrayArgumentReference, Object>();
4342 		for (IndirectArrayArgumentReference reference : elementArgumentReferences) {
4343 			if (reference.isAttached()) {
4344 				values.put(reference, reference.currentValue());
4345 			}
4346 		}
4347 		return values;
4348 	}
4349 
4350 	private void detachReplacedArrayArgumentReferences(
4351 			Map<IndirectArrayArgumentReference, Object> attachedValues) {
4352 		for (Map.Entry<IndirectArrayArgumentReference, Object> entry : attachedValues.entrySet()) {
4353 			entry.getKey().detachIfReplaced(entry.getValue());
4354 		}
4355 	}
4356 
4357 	private void detachMissingArrayArgumentReferences(Map<Object, Object> map) {
4358 		if (elementArgumentReferences.isEmpty()) {
4359 			return;
4360 		}
4361 		for (IndirectArrayArgumentReference reference : elementArgumentReferences) {
4362 			reference.detachIfMissing(map);
4363 		}
4364 	}
4365 
4366 	private static boolean isUntyped(Object value) {
4367 		return value == null || value instanceof UntypedObject;
4368 	}
4369 
4370 	/**
4371 	 * Casts an AWK value to an associative array.
4372 	 *
4373 	 * @param value value to validate
4374 	 * @return the associative array value
4375 	 * @throws AwkRuntimeException when {@code value} is scalar
4376 	 */
4377 	private Map<Object, Object> toMap(Object value) {
4378 		if (!(value instanceof Map)) {
4379 			throw new AwkRuntimeException("Attempting to use a scalar as an array.");
4380 		}
4381 		@SuppressWarnings("unchecked")
4382 		Map<Object, Object> map = (Map<Object, Object>) value;
4383 		return map;
4384 	}
4385 
4386 	/**
4387 	 * Ensures a value is scalar before using it in a scalar-only context such as
4388 	 * a subscript component.
4389 	 *
4390 	 * @param value value to validate
4391 	 * @throws AwkRuntimeException when {@code value} is an array
4392 	 */
4393 	private void checkScalar(Object value) {
4394 		if (value instanceof Map) {
4395 			throw new AwkRuntimeException("Attempting to use an array in a scalar context.");
4396 		}
4397 	}
4398 
4399 	/**
4400 	 * Returns the nested associative array stored in {@code map[key]}, creating it
4401 	 * when the key is undefined.
4402 	 *
4403 	 * @param map containing array
4404 	 * @param key nested-array key
4405 	 * @return the nested associative array stored at {@code key}
4406 	 * @throws AwkRuntimeException when {@code key} is scalar-incompatible or when
4407 	 *         the existing slot contains a scalar
4408 	 */
4409 	private Map<Object, Object> ensureArrayInArray(Map<Object, Object> map, Object key) {
4410 		checkScalar(key);
4411 		boolean existingKey = JRT.containsAwkKey(map, key);
4412 		Object value = JRT.getAssocArrayValue(map, key);
4413 		if (!existingKey || value == null || value instanceof UntypedObject) {
4414 			Map<Object, Object> nested = newAwkArray();
4415 			map.put(key, nested);
4416 			return nested;
4417 		}
4418 		if (!(value instanceof Map)) {
4419 			throw new AwkRuntimeException("Attempting to use a scalar as an array.");
4420 		}
4421 		@SuppressWarnings("unchecked")
4422 		Map<Object, Object> nested = (Map<Object, Object>) value;
4423 		return nested;
4424 	}
4425 
4426 	private Object normalizeExternalVariableValue(Object value) {
4427 		if (value instanceof String) {
4428 			return jrt.toInputScalar(value);
4429 		}
4430 		if (!(value instanceof Map) && !(value instanceof List)) {
4431 			return value;
4432 		}
4433 		return AssocArray.normalizeValue(value, sortedArrayKeys);
4434 	}
4435 
4436 	private static final UninitializedObject BLANK = new UninitializedObject();
4437 
4438 	private interface ArgumentReference {
4439 
4440 		Object snapshot();
4441 
4442 		Object currentValue();
4443 
4444 		void setValue(Object value);
4445 
4446 		void setScalarValue(Object value);
4447 	}
4448 
4449 	private static final class IndirectArgumentReference implements ArgumentReference {
4450 		private final Object[] frame;
4451 		private final long offset;
4452 		private final Object scalarValue;
4453 
4454 		private IndirectArgumentReference(Object[] frameParam, long offsetParam, Object scalarValueParam) {
4455 			frame = frameParam;
4456 			offset = offsetParam;
4457 			scalarValue = scalarValueParam;
4458 		}
4459 
4460 		@Override
4461 		public Object snapshot() {
4462 			return scalarValue;
4463 		}
4464 
4465 		@Override
4466 		public Object currentValue() {
4467 			return frame[(int) offset];
4468 		}
4469 
4470 		@Override
4471 		public void setValue(Object value) {
4472 			frame[(int) offset] = value;
4473 		}
4474 
4475 		@Override
4476 		public void setScalarValue(Object value) {
4477 			if (isUntyped(currentValue())) {
4478 				setValue(value);
4479 			}
4480 		}
4481 	}
4482 
4483 	private static final class IndirectArrayArgumentReference implements ArgumentReference {
4484 		private final Map<Object, Object> map;
4485 		private final Object key;
4486 		private Object detachedValue;
4487 		private boolean detached;
4488 		// Depth of the call frame that received this reference as an
4489 		// argument; negative until the call is entered.
4490 		private int ownerFrame = -1;
4491 
4492 		private IndirectArrayArgumentReference(
4493 				Map<Object, Object> mapParam,
4494 				Object keyParam,
4495 				Object scalarValueParam) {
4496 			map = mapParam;
4497 			key = keyParam;
4498 			detachedValue = scalarValueParam;
4499 			detached = !JRT.containsAwkKey(map, key);
4500 		}
4501 
4502 		@Override
4503 		public Object snapshot() {
4504 			return detachedValue;
4505 		}
4506 
4507 		@Override
4508 		public Object currentValue() {
4509 			return !detached && JRT.containsAwkKey(map, key) ?
4510 					JRT.getAssocArrayValue(map, key) : detachedValue;
4511 		}
4512 
4513 		@Override
4514 		public void setValue(Object value) {
4515 			if (!detached && JRT.containsAwkKey(map, key)) {
4516 				map.put(key, value);
4517 			} else {
4518 				detachedValue = value;
4519 			}
4520 		}
4521 
4522 		@Override
4523 		public void setScalarValue(Object value) {
4524 			if (detached || !JRT.containsAwkKey(map, key)) {
4525 				detachedValue = value;
4526 			} else if (isUntyped(JRT.getAssocArrayValue(map, key))) {
4527 				map.put(key, value);
4528 			}
4529 		}
4530 
4531 		private void detachIfMissing(Map<Object, Object> candidateMap) {
4532 			if (!detached && map == candidateMap && !JRT.containsAwkKey(map, key)) {
4533 				detached = true;
4534 			}
4535 		}
4536 
4537 		private boolean isAttached() {
4538 			return !detached && JRT.containsAwkKey(map, key);
4539 		}
4540 
4541 		private void detachIfReplaced(Object previousValue) {
4542 			if (!detached
4543 					&& (!JRT.containsAwkKey(map, key)
4544 							|| JRT.getAssocArrayValue(map, key) != previousValue)) {
4545 				detached = true;
4546 			}
4547 		}
4548 	}
4549 
4550 	/**
4551 	 * Global names that must not participate in persistent memory even though they
4552 	 * are technically user-visible variables.
4553 	 */
4554 	private static final Set<String> NON_PERSISTENT_GLOBALS = new HashSet<>(
4555 			Arrays
4556 					.asList(
4557 							"ARGV",
4558 							"ARGC",
4559 							"ENVIRON",
4560 							"RSTART",
4561 							"RLENGTH",
4562 							"IGNORECASE",
4563 							"SYMTAB",
4564 							"FUNCTAB"));
4565 
4566 	private static final class SingleRecordInputSource implements InputSource {
4567 
4568 		private final String record;
4569 		private boolean consumed;
4570 
4571 		private SingleRecordInputSource(String record) {
4572 			this.record = record;
4573 		}
4574 
4575 		@Override
4576 		public boolean nextRecord() {
4577 			if (consumed || record == null) {
4578 				return false;
4579 			}
4580 			consumed = true;
4581 			return true;
4582 		}
4583 
4584 		@Override
4585 		public String getRecordText() {
4586 			return consumed ? record : null;
4587 		}
4588 
4589 		@Override
4590 		public List<String> getFields() {
4591 			return null;
4592 		}
4593 
4594 		@Override
4595 		public boolean isFromFilenameList() {
4596 			return false;
4597 		}
4598 	}
4599 
4600 	/**
4601 	 * The value of an address which is not yet assigned a tuple index.
4602 	 */
4603 	public static final int NULL_OFFSET = -1;
4604 
4605 }