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