View Javadoc
1   package io.jawk.intermediate;
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.PrintStream;
26  import java.io.Serializable;
27  import java.util.ArrayDeque;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.Deque;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.IdentityHashMap;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.function.Supplier;
39  import java.util.regex.Pattern;
40  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
41  import io.jawk.ext.ExtensionFunction;
42  import io.jawk.jrt.JRT;
43  
44  /**
45   * <p>
46   * AwkTuples class.
47   * </p>
48   *
49   * @author Danny Daglas
50   */
51  public class AwkTuples implements Serializable {
52  
53  	// Bumped to 6 when the getline opcodes gained a no-record jump address:
54  	// older tuple streams emit them without one and must be recompiled.
55  	// (5 = exact 64-bit integral arithmetic.)
56  	private static final long serialVersionUID = 7L;
57  
58  	/** Address manager */
59  	private final AddressManager addressManager = new AddressManager();
60  
61  	/** Description of the primary script source, used for runtime diagnostics. */
62  	private String sourceDescription;
63  
64  	/**
65  	 * Creates an empty tuple list, ready for the front end to append to.
66  	 */
67  	public AwkTuples() {
68  		// The tuples themselves are appended by the parser.
69  	}
70  
71  	/**
72  	 * Records the description of the primary script source (typically its file
73  	 * name) so runtime diagnostics can point at it.
74  	 *
75  	 * @param sourceDescriptionParam script source description
76  	 */
77  	public void setSourceDescription(String sourceDescriptionParam) {
78  		this.sourceDescription = sourceDescriptionParam;
79  	}
80  
81  	/**
82  	 * Returns the description of the primary script source.
83  	 *
84  	 * @return script source description, or {@code null} when unknown
85  	 */
86  	public String getSourceDescription() {
87  		return sourceDescription;
88  	}
89  
90  	// made public to access static members of AwkTuples via Java Reflection
91  
92  	// made public to be accessable via Java Reflection
93  	// (see toOpcodeString() method below)
94  
95  	/**
96  	 * Override add() to populate the line number for each tuple,
97  	 * rather than polluting all the constructors with this assignment.
98  	 */
99  	/**
100 	 * The tuple queue intentionally uses an {@link ArrayList}. The address mapping
101 	 * logic stores tuple indexes (rather than node references) so that jump targets
102 	 * can be serialized and patched efficiently. A linked list would make every
103 	 * lookup O(n) and complicate address reassignment.
104 	 */
105 	private List<Tuple> queue = new ArrayList<Tuple>(100) {
106 		private static final long serialVersionUID = -6334362156408598578L;
107 
108 		@Override
109 		public boolean add(Tuple t) {
110 			t.setLineNumber(linenoStack.peek());
111 			return super.add(t);
112 		}
113 	};
114 
115 	/** Whether tuple post-processing has already been applied. */
116 	private boolean postProcessed;
117 
118 	/** Whether optimization passes have already been applied. */
119 	private boolean optimized;
120 
121 	/** Whether this tuple stream was produced by {@code compileExpression()}. */
122 	private boolean evalTupleStream;
123 
124 	/**
125 	 * Address of the END blocks section, where a runtime {@code exit} jumps;
126 	 * {@code null} for expression streams. Property addresses are remapped
127 	 * explicitly by the optimizer and seeded as reachability roots, so they
128 	 * stay valid even when no tuple references them.
129 	 */
130 	private Address exitAddress;
131 
132 	/**
133 	 * Address of the ENDFILE section, or {@code null} when the program has
134 	 * no BEGINFILE/ENDFILE rules.
135 	 */
136 	private Address endFileAddress;
137 
138 	/**
139 	 * Address of the {@code NEXT_FILE} tuple that opens each input file, or
140 	 * {@code null} when the program does not use per-file input stepping.
141 	 */
142 	private Address nextFileAddress;
143 
144 	/**
145 	 * Address of the main input loop's next-record entry point, where a
146 	 * runtime {@code next} statement executed from a user-defined function
147 	 * resumes; {@code null} when the program has no main input loop.
148 	 */
149 	private Address nextAddress;
150 
151 	/**
152 	 * <p>
153 	 * toOpcodeString.
154 	 * </p>
155 	 *
156 	 * @param opcode a int
157 	 * @return a {@link java.lang.String} object
158 	 */
159 	public static String toOpcodeString(int opcode) {
160 		return Opcode.fromId(opcode).name();
161 	}
162 
163 	/**
164 	 * <p>
165 	 * pop.
166 	 * </p>
167 	 */
168 	public void pop() {
169 		queue.add(new Tuple.NoOperandTuple(Opcode.POP));
170 	}
171 
172 	/**
173 	 * Discards a value that was evaluated in scalar context.
174 	 */
175 	public void popScalar() {
176 		queue.add(new Tuple.ScalarPopTuple());
177 	}
178 
179 	/**
180 	 * <p>
181 	 * push.
182 	 * </p>
183 	 *
184 	 * @param o a {@link java.lang.Object} object
185 	 */
186 	public void push(Object o) {
187 		if (o instanceof String) {
188 			queue.add(new Tuple.PushStringTuple(o.toString()));
189 		} else if (o instanceof Integer) {
190 			queue.add(new Tuple.PushLongTuple((long) (Integer) o));
191 		} else if (o instanceof Long) {
192 			queue.add(new Tuple.PushLongTuple((long) (Long) o));
193 		} else if (o instanceof Double) {
194 			queue.add(new Tuple.PushDoubleTuple((Double) o));
195 		}
196 	}
197 
198 	/**
199 	 * <p>
200 	 * ifFalse.
201 	 * </p>
202 	 *
203 	 * @param address a {@link io.jawk.intermediate.Address} object
204 	 */
205 	public void ifFalse(Address address) {
206 		queue.add(new Tuple.AddressTuple(Opcode.IFFALSE, address));
207 	}
208 
209 	/**
210 	 * <p>
211 	 * toNumber.
212 	 * </p>
213 	 */
214 	public void toNumber() {
215 		queue.add(new Tuple.NoOperandTuple(Opcode.TO_NUMBER));
216 	}
217 
218 	/**
219 	 * <p>
220 	 * ifTrue.
221 	 * </p>
222 	 *
223 	 * @param address a {@link io.jawk.intermediate.Address} object
224 	 */
225 	public void ifTrue(Address address) {
226 		queue.add(new Tuple.AddressTuple(Opcode.IFTRUE, address));
227 	}
228 
229 	/**
230 	 * <p>
231 	 * gotoAddress.
232 	 * </p>
233 	 *
234 	 * @param address a {@link io.jawk.intermediate.Address} object
235 	 */
236 	public void gotoAddress(Address address) {
237 		queue.add(new Tuple.AddressTuple(Opcode.GOTO, address));
238 	}
239 
240 	/**
241 	 * <p>
242 	 * createAddress.
243 	 * </p>
244 	 *
245 	 * @param label a {@link java.lang.String} object
246 	 * @return a {@link io.jawk.intermediate.Address} object
247 	 */
248 	public Address createAddress(String label) {
249 		return addressManager.createAddress(label);
250 	}
251 
252 	/**
253 	 * <p>
254 	 * address.
255 	 * </p>
256 	 *
257 	 * @param address a {@link io.jawk.intermediate.Address} object
258 	 * @return a {@link io.jawk.intermediate.AwkTuples} object
259 	 */
260 	public AwkTuples address(Address address) {
261 		addressManager.resolveAddress(address, queue.size());
262 		return this;
263 	}
264 
265 	/**
266 	 * <p>
267 	 * nop.
268 	 * </p>
269 	 */
270 	public void nop() {
271 		queue.add(new Tuple.NoOperandTuple(Opcode.NOP));
272 	}
273 
274 	/**
275 	 * <p>
276 	 * print.
277 	 * </p>
278 	 *
279 	 * @param numExprs a int
280 	 */
281 	public void print(int numExprs) {
282 		queue.add(new Tuple.CountTuple(Opcode.PRINT, numExprs));
283 	}
284 
285 	/**
286 	 * <p>
287 	 * printToFile.
288 	 * </p>
289 	 *
290 	 * @param numExprs a int
291 	 * @param append a boolean
292 	 */
293 	public void printToFile(int numExprs, boolean append) {
294 		queue.add(new Tuple.CountAndAppendTuple(Opcode.PRINT_TO_FILE, numExprs, append));
295 	}
296 
297 	/**
298 	 * <p>
299 	 * printToPipe.
300 	 * </p>
301 	 *
302 	 * @param numExprs a int
303 	 */
304 	public void printToPipe(int numExprs) {
305 		queue.add(new Tuple.CountTuple(Opcode.PRINT_TO_PIPE, numExprs));
306 	}
307 
308 	/**
309 	 * <p>
310 	 * printf.
311 	 * </p>
312 	 *
313 	 * @param numExprs a int
314 	 */
315 	public void printf(int numExprs) {
316 		queue.add(new Tuple.CountTuple(Opcode.PRINTF, numExprs));
317 	}
318 
319 	/**
320 	 * <p>
321 	 * printfToFile.
322 	 * </p>
323 	 *
324 	 * @param numExprs a int
325 	 * @param append a boolean
326 	 */
327 	public void printfToFile(int numExprs, boolean append) {
328 		queue.add(new Tuple.CountAndAppendTuple(Opcode.PRINTF_TO_FILE, numExprs, append));
329 	}
330 
331 	/**
332 	 * <p>
333 	 * printfToPipe.
334 	 * </p>
335 	 *
336 	 * @param numExprs a int
337 	 */
338 	public void printfToPipe(int numExprs) {
339 		queue.add(new Tuple.CountTuple(Opcode.PRINTF_TO_PIPE, numExprs));
340 	}
341 
342 	/**
343 	 * <p>
344 	 * sprintf.
345 	 * </p>
346 	 *
347 	 * @param numExprs a int
348 	 */
349 	public void sprintf(int numExprs) {
350 		queue.add(new Tuple.CountTuple(Opcode.SPRINTF, numExprs));
351 	}
352 
353 	/**
354 	 * <p>
355 	 * length.
356 	 * </p>
357 	 *
358 	 * @param numExprs a int
359 	 */
360 	public void length(int numExprs) {
361 		queue.add(new Tuple.CountTuple(Opcode.LENGTH, numExprs));
362 	}
363 
364 	/**
365 	 * <p>
366 	 * concat.
367 	 * </p>
368 	 */
369 	public void concat() {
370 		queue.add(new Tuple.NoOperandTuple(Opcode.CONCAT));
371 	}
372 
373 	/**
374 	 * <p>
375 	 * assign.
376 	 * </p>
377 	 *
378 	 * @param offset a int
379 	 * @param isGlobal a boolean
380 	 */
381 	public void assign(int offset, boolean isGlobal) {
382 		queue.add(new Tuple.VariableTuple(Opcode.ASSIGN, offset, isGlobal));
383 	}
384 
385 	/**
386 	 * <p>
387 	 * assignArray.
388 	 * </p>
389 	 *
390 	 * @param offset a int
391 	 * @param isGlobal a boolean
392 	 */
393 	public void assignArray(int offset, boolean isGlobal) {
394 		queue.add(new Tuple.VariableTuple(Opcode.ASSIGN_ARRAY, offset, isGlobal));
395 	}
396 
397 	/**
398 	 * Assigns a value to a stack-provided associative-array element.
399 	 */
400 	public void assignMapElement() {
401 		queue.add(new Tuple.NoOperandTuple(Opcode.ASSIGN_MAP_ELEMENT));
402 	}
403 
404 	/**
405 	 * <p>
406 	 * assignAsInput.
407 	 * </p>
408 	 */
409 	public void assignAsInput() {
410 		queue.add(new Tuple.NoOperandTuple(Opcode.ASSIGN_AS_INPUT));
411 	}
412 
413 	/**
414 	 * Marks this tuple stream as an expression-eval program rather than a full
415 	 * AWK script. Eval tuple streams can use small tuple-level optimizations that
416 	 * are unsafe for the general case.
417 	 */
418 	public void markEvalTupleStream() {
419 		evalTupleStream = true;
420 	}
421 
422 	/**
423 	 * <p>
424 	 * assignAsInputField.
425 	 * </p>
426 	 */
427 	public void assignAsInputField() {
428 		queue.add(new Tuple.NoOperandTuple(Opcode.ASSIGN_AS_INPUT_FIELD));
429 	}
430 
431 	/**
432 	 * <p>
433 	 * dereference.
434 	 * </p>
435 	 *
436 	 * @param offset a int
437 	 * @param isArray a boolean
438 	 * @param isGlobal a boolean
439 	 */
440 	public void dereference(int offset, boolean isArray, boolean isGlobal) {
441 		queue.add(new Tuple.DereferenceTuple(offset, isArray, isGlobal));
442 	}
443 
444 	/**
445 	 * Emits a variable read that does not assign a blank value when the variable
446 	 * is still untyped.
447 	 * <p>
448 	 * This is used by extension functions such as gawk's {@code typeof()} that
449 	 * need the current lvalue state, not AWK's normal scalar autovivification side
450 	 * effect.
451 	 * </p>
452 	 *
453 	 * @param offset variable offset
454 	 * @param isGlobal whether the variable is global
455 	 */
456 	public void peekDereference(int offset, boolean isGlobal) {
457 		queue.add(new Tuple.VariableTuple(Opcode.PEEK_DEREFERENCE, offset, isGlobal));
458 	}
459 
460 	/**
461 	 * Emits a variable reference whose scalar value is captured immediately while
462 	 * retaining the variable location for a runtime-selected array parameter.
463 	 *
464 	 * @param offset variable offset
465 	 * @param isGlobal whether the variable is global
466 	 */
467 	public void pushIndirectArgument(int offset, boolean isGlobal) {
468 		queue.add(new Tuple.VariableTuple(Opcode.PUSH_INDIRECT_ARGUMENT, offset, isGlobal));
469 	}
470 
471 	/**
472 	 * Emits an indirect-call subarray argument after its containing map and key.
473 	 */
474 	public void pushIndirectArrayArgument() {
475 		queue.add(new Tuple.NoOperandTuple(Opcode.PUSH_INDIRECT_ARRAY_ARGUMENT));
476 	}
477 
478 	/**
479 	 * <p>
480 	 * plusEq.
481 	 * </p>
482 	 *
483 	 * @param offset a int
484 	 * @param isGlobal a boolean
485 	 */
486 	public void plusEq(int offset, boolean isGlobal) {
487 		queue.add(new Tuple.CompoundAssignTuple(Opcode.PLUS_EQ, offset, isGlobal));
488 	}
489 
490 	/**
491 	 * <p>
492 	 * minusEq.
493 	 * </p>
494 	 *
495 	 * @param offset a int
496 	 * @param isGlobal a boolean
497 	 */
498 	public void minusEq(int offset, boolean isGlobal) {
499 		queue.add(new Tuple.CompoundAssignTuple(Opcode.MINUS_EQ, offset, isGlobal));
500 	}
501 
502 	/**
503 	 * <p>
504 	 * multEq.
505 	 * </p>
506 	 *
507 	 * @param offset a int
508 	 * @param isGlobal a boolean
509 	 */
510 	public void multEq(int offset, boolean isGlobal) {
511 		queue.add(new Tuple.CompoundAssignTuple(Opcode.MULT_EQ, offset, isGlobal));
512 	}
513 
514 	/**
515 	 * <p>
516 	 * divEq.
517 	 * </p>
518 	 *
519 	 * @param offset a int
520 	 * @param isGlobal a boolean
521 	 */
522 	public void divEq(int offset, boolean isGlobal) {
523 		queue.add(new Tuple.CompoundAssignTuple(Opcode.DIV_EQ, offset, isGlobal));
524 	}
525 
526 	/**
527 	 * <p>
528 	 * modEq.
529 	 * </p>
530 	 *
531 	 * @param offset a int
532 	 * @param isGlobal a boolean
533 	 */
534 	public void modEq(int offset, boolean isGlobal) {
535 		queue.add(new Tuple.CompoundAssignTuple(Opcode.MOD_EQ, offset, isGlobal));
536 	}
537 
538 	/**
539 	 * <p>
540 	 * powEq.
541 	 * </p>
542 	 *
543 	 * @param offset a int
544 	 * @param isGlobal a boolean
545 	 */
546 	public void powEq(int offset, boolean isGlobal) {
547 		queue.add(new Tuple.CompoundAssignTuple(Opcode.POW_EQ, offset, isGlobal));
548 	}
549 
550 	/**
551 	 * <p>
552 	 * plusEqArray.
553 	 * </p>
554 	 *
555 	 * @param offset a int
556 	 * @param isGlobal a boolean
557 	 */
558 	public void plusEqArray(int offset, boolean isGlobal) {
559 		queue.add(new Tuple.CompoundAssignArrayTuple(Opcode.PLUS_EQ_ARRAY, offset, isGlobal));
560 	}
561 
562 	/**
563 	 * Applies {@code +=} to a stack-provided associative-array element.
564 	 */
565 	public void plusEqMapElement() {
566 		queue.add(new Tuple.CompoundAssignMapElementTuple(Opcode.PLUS_EQ_MAP_ELEMENT));
567 	}
568 
569 	/**
570 	 * <p>
571 	 * minusEqArray.
572 	 * </p>
573 	 *
574 	 * @param offset a int
575 	 * @param isGlobal a boolean
576 	 */
577 	public void minusEqArray(int offset, boolean isGlobal) {
578 		queue.add(new Tuple.CompoundAssignArrayTuple(Opcode.MINUS_EQ_ARRAY, offset, isGlobal));
579 	}
580 
581 	/**
582 	 * Applies {@code -=} to a stack-provided associative-array element.
583 	 */
584 	public void minusEqMapElement() {
585 		queue.add(new Tuple.CompoundAssignMapElementTuple(Opcode.MINUS_EQ_MAP_ELEMENT));
586 	}
587 
588 	/**
589 	 * <p>
590 	 * multEqArray.
591 	 * </p>
592 	 *
593 	 * @param offset a int
594 	 * @param isGlobal a boolean
595 	 */
596 	public void multEqArray(int offset, boolean isGlobal) {
597 		queue.add(new Tuple.CompoundAssignArrayTuple(Opcode.MULT_EQ_ARRAY, offset, isGlobal));
598 	}
599 
600 	/**
601 	 * Applies {@code *=} to a stack-provided associative-array element.
602 	 */
603 	public void multEqMapElement() {
604 		queue.add(new Tuple.CompoundAssignMapElementTuple(Opcode.MULT_EQ_MAP_ELEMENT));
605 	}
606 
607 	/**
608 	 * <p>
609 	 * divEqArray.
610 	 * </p>
611 	 *
612 	 * @param offset a int
613 	 * @param isGlobal a boolean
614 	 */
615 	public void divEqArray(int offset, boolean isGlobal) {
616 		queue.add(new Tuple.CompoundAssignArrayTuple(Opcode.DIV_EQ_ARRAY, offset, isGlobal));
617 	}
618 
619 	/**
620 	 * Applies {@code /=} to a stack-provided associative-array element.
621 	 */
622 	public void divEqMapElement() {
623 		queue.add(new Tuple.CompoundAssignMapElementTuple(Opcode.DIV_EQ_MAP_ELEMENT));
624 	}
625 
626 	/**
627 	 * <p>
628 	 * modEqArray.
629 	 * </p>
630 	 *
631 	 * @param offset a int
632 	 * @param isGlobal a boolean
633 	 */
634 	public void modEqArray(int offset, boolean isGlobal) {
635 		queue.add(new Tuple.CompoundAssignArrayTuple(Opcode.MOD_EQ_ARRAY, offset, isGlobal));
636 	}
637 
638 	/**
639 	 * Applies {@code %=} to a stack-provided associative-array element.
640 	 */
641 	public void modEqMapElement() {
642 		queue.add(new Tuple.CompoundAssignMapElementTuple(Opcode.MOD_EQ_MAP_ELEMENT));
643 	}
644 
645 	/**
646 	 * <p>
647 	 * powEqArray.
648 	 * </p>
649 	 *
650 	 * @param offset a int
651 	 * @param isGlobal a boolean
652 	 */
653 	public void powEqArray(int offset, boolean isGlobal) {
654 		queue.add(new Tuple.CompoundAssignArrayTuple(Opcode.POW_EQ_ARRAY, offset, isGlobal));
655 	}
656 
657 	/**
658 	 * Applies exponentiation assignment to a stack-provided associative-array
659 	 * element.
660 	 */
661 	public void powEqMapElement() {
662 		queue.add(new Tuple.CompoundAssignMapElementTuple(Opcode.POW_EQ_MAP_ELEMENT));
663 	}
664 
665 	/**
666 	 * <p>
667 	 * plusEqInputField.
668 	 * </p>
669 	 */
670 	public void plusEqInputField() {
671 		queue.add(new Tuple.CompoundAssignInputFieldTuple(Opcode.PLUS_EQ_INPUT_FIELD));
672 	}
673 
674 	/**
675 	 * <p>
676 	 * minusEqInputField.
677 	 * </p>
678 	 */
679 	public void minusEqInputField() {
680 		queue.add(new Tuple.CompoundAssignInputFieldTuple(Opcode.MINUS_EQ_INPUT_FIELD));
681 	}
682 
683 	/**
684 	 * <p>
685 	 * multEqInputField.
686 	 * </p>
687 	 */
688 	public void multEqInputField() {
689 		queue.add(new Tuple.CompoundAssignInputFieldTuple(Opcode.MULT_EQ_INPUT_FIELD));
690 	}
691 
692 	/**
693 	 * <p>
694 	 * divEqInputField.
695 	 * </p>
696 	 */
697 	public void divEqInputField() {
698 		queue.add(new Tuple.CompoundAssignInputFieldTuple(Opcode.DIV_EQ_INPUT_FIELD));
699 	}
700 
701 	/**
702 	 * <p>
703 	 * modEqInputField.
704 	 * </p>
705 	 */
706 	public void modEqInputField() {
707 		queue.add(new Tuple.CompoundAssignInputFieldTuple(Opcode.MOD_EQ_INPUT_FIELD));
708 	}
709 
710 	/**
711 	 * <p>
712 	 * powEqInputField.
713 	 * </p>
714 	 */
715 	public void powEqInputField() {
716 		queue.add(new Tuple.CompoundAssignInputFieldTuple(Opcode.POW_EQ_INPUT_FIELD));
717 	}
718 
719 	/**
720 	 * <p>
721 	 * srand.
722 	 * </p>
723 	 *
724 	 * @param num a int
725 	 */
726 	public void srand(int num) {
727 		queue.add(new Tuple.CountTuple(Opcode.SRAND, num));
728 	}
729 
730 	/**
731 	 * <p>
732 	 * rand.
733 	 * </p>
734 	 */
735 	public void rand() {
736 		queue.add(new Tuple.NoOperandTuple(Opcode.RAND));
737 	}
738 
739 	/**
740 	 * <p>
741 	 * intFunc.
742 	 * </p>
743 	 */
744 	public void intFunc() {
745 		queue.add(new Tuple.NoOperandTuple(Opcode.INTFUNC));
746 	}
747 
748 	/**
749 	 * <p>
750 	 * sqrt.
751 	 * </p>
752 	 */
753 	public void sqrt() {
754 		queue.add(new Tuple.NoOperandTuple(Opcode.SQRT));
755 	}
756 
757 	/**
758 	 * <p>
759 	 * log.
760 	 * </p>
761 	 */
762 	public void log() {
763 		queue.add(new Tuple.NoOperandTuple(Opcode.LOG));
764 	}
765 
766 	/**
767 	 * <p>
768 	 * exp.
769 	 * </p>
770 	 */
771 	public void exp() {
772 		queue.add(new Tuple.NoOperandTuple(Opcode.EXP));
773 	}
774 
775 	/**
776 	 * <p>
777 	 * sin.
778 	 * </p>
779 	 */
780 	public void sin() {
781 		queue.add(new Tuple.NoOperandTuple(Opcode.SIN));
782 	}
783 
784 	/**
785 	 * <p>
786 	 * cos.
787 	 * </p>
788 	 */
789 	public void cos() {
790 		queue.add(new Tuple.NoOperandTuple(Opcode.COS));
791 	}
792 
793 	/**
794 	 * <p>
795 	 * atan2.
796 	 * </p>
797 	 */
798 	public void atan2() {
799 		queue.add(new Tuple.NoOperandTuple(Opcode.ATAN2));
800 	}
801 
802 	/**
803 	 * <p>
804 	 * match.
805 	 * </p>
806 	 */
807 	public void match() {
808 		queue.add(new Tuple.NoOperandTuple(Opcode.MATCH));
809 	}
810 
811 	/**
812 	 * <p>
813 	 * index.
814 	 * </p>
815 	 */
816 	public void index() {
817 		queue.add(new Tuple.NoOperandTuple(Opcode.INDEX));
818 	}
819 
820 	/**
821 	 * <p>
822 	 * subForDollar0.
823 	 * </p>
824 	 *
825 	 * @param isGsub a boolean
826 	 */
827 	public void subForDollar0(boolean isGsub) {
828 		queue.add(new Tuple.BooleanTuple(Opcode.SUB_FOR_DOLLAR_0, isGsub));
829 	}
830 
831 	/**
832 	 * <p>
833 	 * subForDollarReference.
834 	 * </p>
835 	 *
836 	 * @param isGsub a boolean
837 	 */
838 	public void subForDollarReference(boolean isGsub) {
839 		queue.add(new Tuple.BooleanTuple(Opcode.SUB_FOR_DOLLAR_REFERENCE, isGsub));
840 	}
841 
842 	/**
843 	 * <p>
844 	 * subForVariable.
845 	 * </p>
846 	 *
847 	 * @param offset a int
848 	 * @param isGlobal a boolean
849 	 * @param isGsub a boolean
850 	 */
851 	public void subForVariable(int offset, boolean isGlobal, boolean isGsub) {
852 		queue.add(new Tuple.SubstitutionVariableTuple(Opcode.SUB_FOR_VARIABLE, offset, isGlobal, isGsub));
853 	}
854 
855 	/**
856 	 * <p>
857 	 * subForArrayReference.
858 	 * </p>
859 	 *
860 	 * @param offset a int
861 	 * @param isGlobal a boolean
862 	 * @param isGsub a boolean
863 	 */
864 	public void subForArrayReference(int offset, boolean isGlobal, boolean isGsub) {
865 		queue.add(new Tuple.SubstitutionVariableTuple(Opcode.SUB_FOR_ARRAY_REFERENCE, offset, isGlobal, isGsub));
866 	}
867 
868 	/**
869 	 * Applies {@code sub}/{@code gsub} to a stack-provided associative-array
870 	 * element.
871 	 *
872 	 * @param isGsub {@code true} for {@code gsub}, {@code false} for {@code sub}
873 	 */
874 	public void subForMapReference(boolean isGsub) {
875 		queue.add(new Tuple.BooleanTuple(Opcode.SUB_FOR_MAP_REFERENCE, isGsub));
876 	}
877 
878 	/**
879 	 * <p>
880 	 * split.
881 	 * </p>
882 	 *
883 	 * @param numargs a int
884 	 */
885 	public void split(int numargs) {
886 		queue.add(new Tuple.CountTuple(Opcode.SPLIT, numargs));
887 	}
888 
889 	/**
890 	 * <p>
891 	 * substr.
892 	 * </p>
893 	 *
894 	 * @param numargs a int
895 	 */
896 	public void substr(int numargs) {
897 		queue.add(new Tuple.CountTuple(Opcode.SUBSTR, numargs));
898 	}
899 
900 	/**
901 	 * <p>
902 	 * tolower.
903 	 * </p>
904 	 */
905 	public void tolower() {
906 		queue.add(new Tuple.NoOperandTuple(Opcode.TOLOWER));
907 	}
908 
909 	/**
910 	 * <p>
911 	 * toupper.
912 	 * </p>
913 	 */
914 	public void toupper() {
915 		queue.add(new Tuple.NoOperandTuple(Opcode.TOUPPER));
916 	}
917 
918 	/**
919 	 * <p>
920 	 * system.
921 	 * </p>
922 	 */
923 	public void system() {
924 		queue.add(new Tuple.NoOperandTuple(Opcode.SYSTEM));
925 	}
926 
927 	/**
928 	 * <p>
929 	 * swap.
930 	 * </p>
931 	 */
932 	public void swap() {
933 		queue.add(new Tuple.NoOperandTuple(Opcode.SWAP));
934 	}
935 
936 	/**
937 	 * <p>
938 	 * add.
939 	 * </p>
940 	 */
941 	public void add() {
942 		queue.add(new Tuple.NoOperandTuple(Opcode.ADD));
943 	}
944 
945 	/**
946 	 * <p>
947 	 * subtract.
948 	 * </p>
949 	 */
950 	public void subtract() {
951 		queue.add(new Tuple.NoOperandTuple(Opcode.SUBTRACT));
952 	}
953 
954 	/**
955 	 * <p>
956 	 * multiply.
957 	 * </p>
958 	 */
959 	public void multiply() {
960 		queue.add(new Tuple.NoOperandTuple(Opcode.MULTIPLY));
961 	}
962 
963 	/**
964 	 * <p>
965 	 * divide.
966 	 * </p>
967 	 */
968 	public void divide() {
969 		queue.add(new Tuple.NoOperandTuple(Opcode.DIVIDE));
970 	}
971 
972 	/**
973 	 * <p>
974 	 * mod.
975 	 * </p>
976 	 */
977 	public void mod() {
978 		queue.add(new Tuple.NoOperandTuple(Opcode.MOD));
979 	}
980 
981 	/**
982 	 * <p>
983 	 * pow.
984 	 * </p>
985 	 */
986 	public void pow() {
987 		queue.add(new Tuple.NoOperandTuple(Opcode.POW));
988 	}
989 
990 	/**
991 	 * <p>
992 	 * inc.
993 	 * </p>
994 	 *
995 	 * @param offset a int
996 	 * @param isGlobal a boolean
997 	 */
998 	public void inc(int offset, boolean isGlobal) {
999 		queue.add(new Tuple.VariableTuple(Opcode.INC, offset, isGlobal));
1000 	}
1001 
1002 	/**
1003 	 * <p>
1004 	 * dec.
1005 	 * </p>
1006 	 *
1007 	 * @param offset a int
1008 	 * @param isGlobal a boolean
1009 	 */
1010 	public void dec(int offset, boolean isGlobal) {
1011 		queue.add(new Tuple.VariableTuple(Opcode.DEC, offset, isGlobal));
1012 	}
1013 
1014 	/**
1015 	 * <p>
1016 	 * postInc.
1017 	 * </p>
1018 	 *
1019 	 * @param offset a int
1020 	 * @param isGlobal a boolean
1021 	 */
1022 	public void postInc(int offset, boolean isGlobal) {
1023 		queue.add(new Tuple.VariableTuple(Opcode.POSTINC, offset, isGlobal));
1024 	}
1025 
1026 	/**
1027 	 * <p>
1028 	 * postDec.
1029 	 * </p>
1030 	 *
1031 	 * @param offset a int
1032 	 * @param isGlobal a boolean
1033 	 */
1034 	public void postDec(int offset, boolean isGlobal) {
1035 		queue.add(new Tuple.VariableTuple(Opcode.POSTDEC, offset, isGlobal));
1036 	}
1037 
1038 	/**
1039 	 * <p>
1040 	 * incArrayRef.
1041 	 * </p>
1042 	 *
1043 	 * @param offset a int
1044 	 * @param isGlobal a boolean
1045 	 */
1046 	public void incArrayRef(int offset, boolean isGlobal) {
1047 		queue.add(new Tuple.VariableTuple(Opcode.INC_ARRAY_REF, offset, isGlobal));
1048 	}
1049 
1050 	/**
1051 	 * Increments a stack-provided associative-array element reference.
1052 	 */
1053 	public void incMapRef() {
1054 		queue.add(new Tuple.NoOperandTuple(Opcode.INC_MAP_REF));
1055 	}
1056 
1057 	/**
1058 	 * <p>
1059 	 * decArrayRef.
1060 	 * </p>
1061 	 *
1062 	 * @param offset a int
1063 	 * @param isGlobal a boolean
1064 	 */
1065 	public void decArrayRef(int offset, boolean isGlobal) {
1066 		queue.add(new Tuple.VariableTuple(Opcode.DEC_ARRAY_REF, offset, isGlobal));
1067 	}
1068 
1069 	/**
1070 	 * Decrements a stack-provided associative-array element reference.
1071 	 */
1072 	public void decMapRef() {
1073 		queue.add(new Tuple.NoOperandTuple(Opcode.DEC_MAP_REF));
1074 	}
1075 
1076 	/**
1077 	 * <p>
1078 	 * incDollarRef.
1079 	 * </p>
1080 	 */
1081 	public void incDollarRef() {
1082 		queue.add(new Tuple.NoOperandTuple(Opcode.INC_DOLLAR_REF));
1083 	}
1084 
1085 	/**
1086 	 * <p>
1087 	 * decDollarRef.
1088 	 * </p>
1089 	 */
1090 	public void decDollarRef() {
1091 		queue.add(new Tuple.NoOperandTuple(Opcode.DEC_DOLLAR_REF));
1092 	}
1093 
1094 	/**
1095 	 * <p>
1096 	 * dup.
1097 	 * </p>
1098 	 */
1099 	public void dup() {
1100 		queue.add(new Tuple.NoOperandTuple(Opcode.DUP));
1101 	}
1102 
1103 	/**
1104 	 * <p>
1105 	 * not.
1106 	 * </p>
1107 	 */
1108 	public void not() {
1109 		queue.add(new Tuple.NoOperandTuple(Opcode.NOT));
1110 	}
1111 
1112 	/**
1113 	 * <p>
1114 	 * negate.
1115 	 * </p>
1116 	 */
1117 	public void negate() {
1118 		queue.add(new Tuple.NoOperandTuple(Opcode.NEGATE));
1119 	}
1120 
1121 	/**
1122 	 * <p>
1123 	 * unary plus.
1124 	 * </p>
1125 	 */
1126 	public void unaryPlus() {
1127 		queue.add(new Tuple.NoOperandTuple(Opcode.UNARY_PLUS));
1128 	}
1129 
1130 	/**
1131 	 * <p>
1132 	 * cmpEq.
1133 	 * </p>
1134 	 */
1135 	public void cmpEq() {
1136 		queue.add(new Tuple.NoOperandTuple(Opcode.CMP_EQ));
1137 	}
1138 
1139 	/**
1140 	 * <p>
1141 	 * cmpLt.
1142 	 * </p>
1143 	 */
1144 	public void cmpLt() {
1145 		queue.add(new Tuple.NoOperandTuple(Opcode.CMP_LT));
1146 	}
1147 
1148 	/**
1149 	 * <p>
1150 	 * cmpGt.
1151 	 * </p>
1152 	 */
1153 	public void cmpGt() {
1154 		queue.add(new Tuple.NoOperandTuple(Opcode.CMP_GT));
1155 	}
1156 
1157 	/**
1158 	 * <p>
1159 	 * matches.
1160 	 * </p>
1161 	 */
1162 	public void matches() {
1163 		queue.add(new Tuple.NoOperandTuple(Opcode.MATCHES));
1164 	}
1165 
1166 	/**
1167 	 * <p>
1168 	 * dereferenceArray.
1169 	 * </p>
1170 	 */
1171 	public void dereferenceArray() {
1172 		queue.add(new Tuple.NoOperandTuple(Opcode.DEREF_ARRAY));
1173 	}
1174 
1175 	/**
1176 	 * Looks up an associative-array element without creating a blank entry when
1177 	 * the key is missing.
1178 	 */
1179 	public void peekArrayElement() {
1180 		queue.add(new Tuple.NoOperandTuple(Opcode.PEEK_ARRAY_ELEMENT));
1181 	}
1182 
1183 	/**
1184 	 * Dereferences an associative-array element as a nested array, creating it if
1185 	 * needed.
1186 	 */
1187 	public void ensureArrayElement() {
1188 		queue.add(new Tuple.NoOperandTuple(Opcode.ENSURE_ARRAY_ELEMENT));
1189 	}
1190 
1191 	/**
1192 	 * <p>
1193 	 * key list.
1194 	 * </p>
1195 	 */
1196 	public void keylist() {
1197 		queue.add(new Tuple.NoOperandTuple(Opcode.KEYLIST));
1198 	}
1199 
1200 	/**
1201 	 * <p>
1202 	 * isEmptyList.
1203 	 * </p>
1204 	 *
1205 	 * @param address a {@link io.jawk.intermediate.Address} object
1206 	 */
1207 	public void isEmptyList(Address address) {
1208 		queue.add(new Tuple.AddressTuple(Opcode.IS_EMPTY_KEYLIST, address));
1209 	}
1210 
1211 	/**
1212 	 * <p>
1213 	 * getFirstAndRemoveFromList.
1214 	 * </p>
1215 	 */
1216 	public void getFirstAndRemoveFromList() {
1217 		queue.add(new Tuple.NoOperandTuple(Opcode.GET_FIRST_AND_REMOVE_FROM_KEYLIST));
1218 	}
1219 
1220 	/**
1221 	 * <p>
1222 	 * checkClass.
1223 	 * </p>
1224 	 *
1225 	 * @param cls a {@link java.lang.Class} object
1226 	 * @return a boolean
1227 	 */
1228 	public boolean checkClass(Class<?> cls) {
1229 		queue.add(new Tuple.ClassTuple(cls));
1230 		return true;
1231 	}
1232 
1233 	/**
1234 	 * <p>
1235 	 * getInputField.
1236 	 * </p>
1237 	 */
1238 	public void getInputField() {
1239 		queue.add(new Tuple.NoOperandTuple(Opcode.GET_INPUT_FIELD));
1240 	}
1241 
1242 	/**
1243 	 * <p>
1244 	 * getInputField.
1245 	 * </p>
1246 	 *
1247 	 * @param fieldIndex a long
1248 	 */
1249 	public void getInputField(long fieldIndex) {
1250 		queue.add(new Tuple.InputFieldTuple(fieldIndex));
1251 	}
1252 
1253 	/**
1254 	 * <p>
1255 	 * consumeInput.
1256 	 * </p>
1257 	 *
1258 	 * @param address a {@link io.jawk.intermediate.Address} object
1259 	 */
1260 	public void consumeInput(Address address) {
1261 		queue.add(new Tuple.AddressTuple(Opcode.CONSUME_INPUT, address));
1262 	}
1263 
1264 	/**
1265 	 * <p>
1266 	 * getlineInput.
1267 	 * </p>
1268 	 */
1269 	public void getlineInput() {
1270 		queue.add(new Tuple.NoOperandTuple(Opcode.GETLINE_INPUT));
1271 	}
1272 
1273 	/**
1274 	 * Reads one record of the main input for {@code getline target} without
1275 	 * touching the current record.
1276 	 *
1277 	 * @param noRecordAddress address to jump to when no record was read, with
1278 	 *        only the return code pushed
1279 	 */
1280 	public void getlineInputToTarget(Address noRecordAddress) {
1281 		queue.add(new Tuple.AddressTuple(Opcode.GETLINE_INPUT_TO_TARGET, noRecordAddress));
1282 	}
1283 
1284 	/**
1285 	 * Reads one record from a file for a redirected {@code getline}.
1286 	 *
1287 	 * @param noRecordAddress address to jump to when no record was read, with
1288 	 *        only the return code pushed
1289 	 */
1290 	public void useAsFileInput(Address noRecordAddress) {
1291 		queue.add(new Tuple.AddressTuple(Opcode.USE_AS_FILE_INPUT, noRecordAddress));
1292 	}
1293 
1294 	/**
1295 	 * Reads one record from the output of a command for a redirected
1296 	 * {@code getline}.
1297 	 *
1298 	 * @param noRecordAddress address to jump to when no record was read, with
1299 	 *        only the return code pushed
1300 	 */
1301 	public void useAsCommandInput(Address noRecordAddress) {
1302 		queue.add(new Tuple.AddressTuple(Opcode.USE_AS_COMMAND_INPUT, noRecordAddress));
1303 	}
1304 
1305 	/**
1306 	 * <p>
1307 	 * nfOffset.
1308 	 * </p>
1309 	 *
1310 	 * @param offset a int
1311 	 */
1312 	public void nfOffset(int offset) {
1313 		queue.add(new Tuple.LongTuple(Opcode.NF_OFFSET, offset));
1314 	}
1315 
1316 	/**
1317 	 * <p>
1318 	 * nrOffset.
1319 	 * </p>
1320 	 *
1321 	 * @param offset a int
1322 	 */
1323 	public void nrOffset(int offset) {
1324 		queue.add(new Tuple.LongTuple(Opcode.NR_OFFSET, offset));
1325 	}
1326 
1327 	/**
1328 	 * <p>
1329 	 * fnrOffset.
1330 	 * </p>
1331 	 *
1332 	 * @param offset a int
1333 	 */
1334 	public void fnrOffset(int offset) {
1335 		queue.add(new Tuple.LongTuple(Opcode.FNR_OFFSET, offset));
1336 	}
1337 
1338 	/**
1339 	 * <p>
1340 	 * fsOffset.
1341 	 * </p>
1342 	 *
1343 	 * @param offset a int
1344 	 */
1345 	public void fsOffset(int offset) {
1346 		queue.add(new Tuple.LongTuple(Opcode.FS_OFFSET, offset));
1347 	}
1348 
1349 	/**
1350 	 * <p>
1351 	 * rsOffset.
1352 	 * </p>
1353 	 *
1354 	 * @param offset a int
1355 	 */
1356 	public void rsOffset(int offset) {
1357 		queue.add(new Tuple.LongTuple(Opcode.RS_OFFSET, offset));
1358 	}
1359 
1360 	/**
1361 	 * <p>
1362 	 * ofsOffset.
1363 	 * </p>
1364 	 *
1365 	 * @param offset a int
1366 	 */
1367 	public void ofsOffset(int offset) {
1368 		queue.add(new Tuple.LongTuple(Opcode.OFS_OFFSET, offset));
1369 	}
1370 
1371 	/**
1372 	 * <p>
1373 	 * orsOffset.
1374 	 * </p>
1375 	 *
1376 	 * @param offset a int
1377 	 */
1378 	public void orsOffset(int offset) {
1379 		queue.add(new Tuple.LongTuple(Opcode.ORS_OFFSET, offset));
1380 	}
1381 
1382 	/**
1383 	 * <p>
1384 	 * rstartOffset.
1385 	 * </p>
1386 	 *
1387 	 * @param offset a int
1388 	 */
1389 	public void rstartOffset(int offset) {
1390 		queue.add(new Tuple.LongTuple(Opcode.RSTART_OFFSET, offset));
1391 	}
1392 
1393 	/**
1394 	 * <p>
1395 	 * rlengthOffset.
1396 	 * </p>
1397 	 *
1398 	 * @param offset a int
1399 	 */
1400 	public void rlengthOffset(int offset) {
1401 		queue.add(new Tuple.LongTuple(Opcode.RLENGTH_OFFSET, offset));
1402 	}
1403 
1404 	/**
1405 	 * <p>
1406 	 * filenameOffset.
1407 	 * </p>
1408 	 *
1409 	 * @param offset a int
1410 	 */
1411 	public void filenameOffset(int offset) {
1412 		queue.add(new Tuple.LongTuple(Opcode.FILENAME_OFFSET, offset));
1413 	}
1414 
1415 	/**
1416 	 * <p>
1417 	 * subsepOffset.
1418 	 * </p>
1419 	 *
1420 	 * @param offset a int
1421 	 */
1422 	public void subsepOffset(int offset) {
1423 		queue.add(new Tuple.LongTuple(Opcode.SUBSEP_OFFSET, offset));
1424 	}
1425 
1426 	/**
1427 	 * <p>
1428 	 * convfmtOffset.
1429 	 * </p>
1430 	 *
1431 	 * @param offset a int
1432 	 */
1433 	public void convfmtOffset(int offset) {
1434 		queue.add(new Tuple.LongTuple(Opcode.CONVFMT_OFFSET, offset));
1435 	}
1436 
1437 	/**
1438 	 * <p>
1439 	 * ofmtOffset.
1440 	 * </p>
1441 	 *
1442 	 * @param offset a int
1443 	 */
1444 	public void ofmtOffset(int offset) {
1445 		queue.add(new Tuple.LongTuple(Opcode.OFMT_OFFSET, offset));
1446 	}
1447 
1448 	/**
1449 	 * <p>
1450 	 * environOffset.
1451 	 * </p>
1452 	 *
1453 	 * @param offset a int
1454 	 */
1455 	public void environOffset(int offset) {
1456 		queue.add(new Tuple.LongTuple(Opcode.ENVIRON_OFFSET, offset));
1457 	}
1458 
1459 	/**
1460 	 * Emits the tuple that runs the extension beforeStart hooks, placed at the
1461 	 * end of the preamble.
1462 	 */
1463 	public void beforeStartHooks() {
1464 		queue.add(new Tuple.NoOperandTuple(Opcode.BEFORE_START_HOOKS));
1465 	}
1466 
1467 	/**
1468 	 * Emits the tuple populating the SYMTAB array.
1469 	 *
1470 	 * @param offset offset of the SYMTAB global
1471 	 */
1472 	public void updateSymtab(int offset) {
1473 		queue.add(new Tuple.LongTuple(Opcode.UPDATE_SYMTAB, offset));
1474 	}
1475 
1476 	/**
1477 	 * Emits the tuple populating the FUNCTAB array.
1478 	 *
1479 	 * @param offset offset of the FUNCTAB global
1480 	 */
1481 	public void updateFunctab(int offset) {
1482 		queue.add(new Tuple.LongTuple(Opcode.UPDATE_FUNCTAB, offset));
1483 	}
1484 
1485 	/**
1486 	 * <p>
1487 	 * argcOffset.
1488 	 * </p>
1489 	 *
1490 	 * @param offset a int
1491 	 */
1492 	public void argcOffset(int offset) {
1493 		queue.add(new Tuple.LongTuple(Opcode.ARGC_OFFSET, offset));
1494 	}
1495 
1496 	/**
1497 	 * <p>
1498 	 * argvOffset.
1499 	 * </p>
1500 	 *
1501 	 * @param offset a int
1502 	 */
1503 	public void argvOffset(int offset) {
1504 		queue.add(new Tuple.LongTuple(Opcode.ARGV_OFFSET, offset));
1505 	}
1506 
1507 	// JRT-managed special variable helpers
1508 	/** Pushes the current value of {@code NF} onto the operand stack. */
1509 	public void pushNF() {
1510 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_NF));
1511 	}
1512 
1513 	/** Assigns the top-of-stack value to {@code NF}. */
1514 	public void assignNF() {
1515 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_NF));
1516 	}
1517 
1518 	/** Pushes the current value of {@code NR} onto the operand stack. */
1519 	public void pushNR() {
1520 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_NR));
1521 	}
1522 
1523 	/** Assigns the top-of-stack value to {@code NR}. */
1524 	public void assignNR() {
1525 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_NR));
1526 	}
1527 
1528 	/** Pushes the current value of {@code FNR} onto the operand stack. */
1529 	public void pushFNR() {
1530 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_FNR));
1531 	}
1532 
1533 	/** Assigns the top-of-stack value to {@code FNR}. */
1534 	public void assignFNR() {
1535 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_FNR));
1536 	}
1537 
1538 	/** Pushes the current value of {@code FS} onto the operand stack. */
1539 	public void pushFS() {
1540 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_FS));
1541 	}
1542 
1543 	/** Assigns the top-of-stack value to {@code FS}. */
1544 	public void assignFS() {
1545 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_FS));
1546 	}
1547 
1548 	/**
1549 	 * Emits a tuple pushing the value of IGNORECASE.
1550 	 */
1551 	public void pushIGNORECASE() {
1552 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_IGNORECASE));
1553 	}
1554 
1555 	/**
1556 	 * Emits a tuple assigning the top of the stack to IGNORECASE.
1557 	 */
1558 	public void assignIGNORECASE() {
1559 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_IGNORECASE));
1560 	}
1561 
1562 	/**
1563 	 * Emits the tuple pushing the value of ERRNO, managed by the JRT.
1564 	 */
1565 	public void pushERRNO() {
1566 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_ERRNO));
1567 	}
1568 
1569 	/**
1570 	 * Emits the tuple assigning the top of the stack to ERRNO, managed by the
1571 	 * JRT.
1572 	 */
1573 	public void assignERRNO() {
1574 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_ERRNO));
1575 	}
1576 
1577 	/**
1578 	 * Emits the tuple pushing the value of ARGIND, managed by the JRT.
1579 	 */
1580 	public void pushARGIND() {
1581 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_ARGIND));
1582 	}
1583 
1584 	/**
1585 	 * Emits the tuple assigning the top of the stack to ARGIND, managed by
1586 	 * the JRT.
1587 	 */
1588 	public void assignARGIND() {
1589 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_ARGIND));
1590 	}
1591 
1592 	/** Pushes the current value of {@code RS} onto the operand stack. */
1593 	public void pushRS() {
1594 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_RS));
1595 	}
1596 
1597 	/** Assigns the top-of-stack value to {@code RS}. */
1598 	public void assignRS() {
1599 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_RS));
1600 	}
1601 
1602 	/** Pushes the current value of {@code OFS} onto the operand stack. */
1603 	public void pushOFS() {
1604 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_OFS));
1605 	}
1606 
1607 	/** Assigns the top-of-stack value to {@code OFS}. */
1608 	public void assignOFS() {
1609 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_OFS));
1610 	}
1611 
1612 	/** Pushes the current value of {@code ORS} onto the operand stack. */
1613 	public void pushORS() {
1614 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_ORS));
1615 	}
1616 
1617 	/** Assigns the top-of-stack value to {@code ORS}. */
1618 	public void assignORS() {
1619 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_ORS));
1620 	}
1621 
1622 	/** Pushes the current value of {@code RSTART} onto the operand stack. */
1623 	public void pushRSTART() {
1624 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_RSTART));
1625 	}
1626 
1627 	/** Assigns the top-of-stack value to {@code RSTART}. */
1628 	public void assignRSTART() {
1629 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_RSTART));
1630 	}
1631 
1632 	/** Pushes the current value of {@code RLENGTH} onto the operand stack. */
1633 	public void pushRLENGTH() {
1634 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_RLENGTH));
1635 	}
1636 
1637 	/** Assigns the top-of-stack value to {@code RLENGTH}. */
1638 	public void assignRLENGTH() {
1639 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_RLENGTH));
1640 	}
1641 
1642 	/** Pushes the current value of {@code FILENAME} onto the operand stack. */
1643 	public void pushFILENAME() {
1644 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_FILENAME));
1645 	}
1646 
1647 	/** Assigns the top-of-stack value to {@code FILENAME}. */
1648 	public void assignFILENAME() {
1649 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_FILENAME));
1650 	}
1651 
1652 	/** Pushes the current value of {@code SUBSEP} onto the operand stack. */
1653 	public void pushSUBSEP() {
1654 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_SUBSEP));
1655 	}
1656 
1657 	/** Assigns the top-of-stack value to {@code SUBSEP}. */
1658 	public void assignSUBSEP() {
1659 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_SUBSEP));
1660 	}
1661 
1662 	/** Pushes the current value of {@code CONVFMT} onto the operand stack. */
1663 	public void pushCONVFMT() {
1664 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_CONVFMT));
1665 	}
1666 
1667 	/** Assigns the top-of-stack value to {@code CONVFMT}. */
1668 	public void assignCONVFMT() {
1669 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_CONVFMT));
1670 	}
1671 
1672 	/** Pushes the current value of {@code OFMT} onto the operand stack. */
1673 	public void pushOFMT() {
1674 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_OFMT));
1675 	}
1676 
1677 	/** Assigns the top-of-stack value to {@code OFMT}. */
1678 	public void assignOFMT() {
1679 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_OFMT));
1680 	}
1681 
1682 	/** Pushes the current value of {@code ARGC} onto the operand stack. */
1683 	public void pushARGC() {
1684 		queue.add(new Tuple.BuiltinVarTuple(Opcode.PUSH_ARGC));
1685 	}
1686 
1687 	/** Assigns the top-of-stack value to {@code ARGC}. */
1688 	public void assignARGC() {
1689 		queue.add(new Tuple.BuiltinVarTuple(Opcode.ASSIGN_ARGC));
1690 	}
1691 
1692 	/**
1693 	 * <p>
1694 	 * applyRS.
1695 	 * </p>
1696 	 */
1697 	public void applyRS() {
1698 		queue.add(new Tuple.NoOperandTuple(Opcode.APPLY_RS));
1699 	}
1700 
1701 	/**
1702 	 * <p>
1703 	 * function.
1704 	 * </p>
1705 	 *
1706 	 * @param funcName a {@link java.lang.String} object
1707 	 * @param numFormalParams a int
1708 	 */
1709 	public void function(String funcName, int numFormalParams) {
1710 		queue.add(new Tuple.FunctionTuple(funcName, numFormalParams));
1711 	}
1712 
1713 	/**
1714 	 * <p>
1715 	 * callFunction.
1716 	 * </p>
1717 	 *
1718 	 * @param addressSupplier supplier resolving the function's entry point
1719 	 * @param funcName a {@link java.lang.String} object
1720 	 * @param numFormalParams a int
1721 	 * @param numActualParams a int
1722 	 */
1723 	public void callFunction(
1724 			Supplier<Address> addressSupplier,
1725 			String funcName,
1726 			int numFormalParams,
1727 			int numActualParams) {
1728 		queue.add(new Tuple.CallFunctionTuple(addressSupplier, funcName, numFormalParams, numActualParams));
1729 	}
1730 
1731 	/**
1732 	 * Emits a call whose function name is evaluated at runtime.
1733 	 *
1734 	 * @param userFunctions available user-defined function targets
1735 	 * @param extensionFunctions available extension function targets
1736 	 * @param numActualParams number of evaluated actual parameters
1737 	 * @param sourceName source name for runtime diagnostics
1738 	 * @param lineNumber source line for runtime diagnostics
1739 	 */
1740 	public void indirectCall(
1741 			Map<String, Tuple.IndirectFunctionTarget> userFunctions,
1742 			Map<String, ExtensionFunction> extensionFunctions,
1743 			int numActualParams,
1744 			String sourceName,
1745 			int lineNumber) {
1746 		queue
1747 				.add(
1748 						new Tuple.IndirectCallTuple(
1749 								userFunctions,
1750 								extensionFunctions,
1751 								numActualParams,
1752 								sourceName,
1753 								lineNumber));
1754 	}
1755 
1756 	/**
1757 	 * Emits a tuple that prints a diagnostic message to the warning stream when
1758 	 * executed. Planted by the parser just before the instruction it describes,
1759 	 * so the warning appears in runtime order, exactly where gawk emits it.
1760 	 *
1761 	 * @param message warning text to print
1762 	 */
1763 	public void warning(String message) {
1764 		queue.add(new Tuple.WarningTuple(message));
1765 	}
1766 
1767 	/**
1768 	 * <p>
1769 	 * setReturnResult.
1770 	 * </p>
1771 	 */
1772 	public void setReturnResult() {
1773 		queue.add(new Tuple.NoOperandTuple(Opcode.SET_RETURN_RESULT));
1774 	}
1775 
1776 	/**
1777 	 * <p>
1778 	 * returnFromFunction.
1779 	 * </p>
1780 	 */
1781 	public void returnFromFunction() {
1782 		queue.add(new Tuple.NoOperandTuple(Opcode.RETURN_FROM_FUNCTION));
1783 	}
1784 
1785 	/**
1786 	 * <p>
1787 	 * setNumGlobals.
1788 	 * </p>
1789 	 *
1790 	 * @param numGlobals a int
1791 	 */
1792 	public void setNumGlobals(int numGlobals) {
1793 		queue.add(new Tuple.CountTuple(Opcode.SET_NUM_GLOBALS, numGlobals));
1794 	}
1795 
1796 	/**
1797 	 * <p>
1798 	 * close.
1799 	 * </p>
1800 	 */
1801 	public void close() {
1802 		queue.add(new Tuple.NoOperandTuple(Opcode.CLOSE));
1803 	}
1804 
1805 	/**
1806 	 * <p>
1807 	 * applySubsep.
1808 	 * </p>
1809 	 *
1810 	 * @param count a int
1811 	 */
1812 	public void applySubsep(int count) {
1813 		queue.add(new Tuple.CountTuple(Opcode.APPLY_SUBSEP, count));
1814 	}
1815 
1816 	/**
1817 	 * <p>
1818 	 * applySubsepUnderTop.
1819 	 * </p>
1820 	 *
1821 	 * @param count a int
1822 	 */
1823 	public void applySubsepUnderTop(int count) {
1824 		queue.add(new Tuple.CountTuple(Opcode.APPLY_SUBSEP_UNDER_TOP, count));
1825 	}
1826 
1827 	/**
1828 	 * <p>
1829 	 * deleteArrayElement.
1830 	 * </p>
1831 	 *
1832 	 * @param offset a int
1833 	 * @param isGlobal a boolean
1834 	 */
1835 	public void deleteArrayElement(int offset, boolean isGlobal) {
1836 		queue.add(new Tuple.VariableTuple(Opcode.DELETE_ARRAY_ELEMENT, offset, isGlobal));
1837 	}
1838 
1839 	/**
1840 	 * Deletes a stack-provided associative-array element.
1841 	 */
1842 	public void deleteMapElement() {
1843 		queue.add(new Tuple.NoOperandTuple(Opcode.DELETE_MAP_ELEMENT));
1844 	}
1845 
1846 	/**
1847 	 * <p>
1848 	 * deleteArray.
1849 	 * </p>
1850 	 *
1851 	 * @param offset a int
1852 	 * @param isGlobal a boolean
1853 	 */
1854 	public void deleteArray(int offset, boolean isGlobal) {
1855 		queue.add(new Tuple.VariableTuple(Opcode.DELETE_ARRAY, offset, isGlobal));
1856 	}
1857 
1858 	/**
1859 	 * Registers the address of the END blocks section, so that a runtime
1860 	 * {@code exit} statement can jump to it. A property of the tuple stream
1861 	 * rather than a tuple: the interpreter reads it once when it installs
1862 	 * the program.
1863 	 *
1864 	 * @param addr address of the END blocks section
1865 	 */
1866 	public void setExitAddress(Address addr) {
1867 		exitAddress = addr;
1868 	}
1869 
1870 	/**
1871 	 * Returns the address of the END blocks section, or {@code null} when
1872 	 * the tuple stream is an expression stream with no END blocks.
1873 	 *
1874 	 * @return address of the END blocks section, or {@code null}
1875 	 */
1876 	public Address getExitAddress() {
1877 		return exitAddress;
1878 	}
1879 
1880 	/**
1881 	 * <p>
1882 	 * setWithinEndBlocks.
1883 	 * </p>
1884 	 *
1885 	 * @param b a boolean
1886 	 */
1887 	public void setWithinEndBlocks(boolean b) {
1888 		queue.add(new Tuple.BooleanTuple(Opcode.SET_WITHIN_END_BLOCKS, b));
1889 	}
1890 
1891 	/**
1892 	 * Registers the address of the ENDFILE section, so that a runtime
1893 	 * {@code nextfile} statement can jump to it. A property of the tuple
1894 	 * stream rather than a tuple: the interpreter reads it once when it
1895 	 * installs the program.
1896 	 *
1897 	 * @param addr address of the ENDFILE section
1898 	 */
1899 	public void setEndFileAddress(Address addr) {
1900 		endFileAddress = addr;
1901 	}
1902 
1903 	/**
1904 	 * Returns the address of the ENDFILE section, or {@code null} when the
1905 	 * program has no BEGINFILE/ENDFILE rules.
1906 	 *
1907 	 * @return address of the ENDFILE section, or {@code null}
1908 	 */
1909 	public Address getEndFileAddress() {
1910 		return endFileAddress;
1911 	}
1912 
1913 	/**
1914 	 * Registers the address of the {@code NEXT_FILE} tuple that opens each
1915 	 * input file, so that a runtime {@code nextfile} statement can bypass
1916 	 * the ENDFILE rules for input files that could not be opened. A property
1917 	 * of the tuple stream rather than a tuple: the interpreter reads it once
1918 	 * when it installs the program.
1919 	 *
1920 	 * @param addr address of the NEXT_FILE tuple
1921 	 */
1922 	public void setNextFileAddress(Address addr) {
1923 		nextFileAddress = addr;
1924 	}
1925 
1926 	/**
1927 	 * Returns the address of the {@code NEXT_FILE} tuple that opens each
1928 	 * input file, or {@code null} when the program does not use per-file
1929 	 * input stepping.
1930 	 *
1931 	 * @return address of the NEXT_FILE tuple, or {@code null}
1932 	 */
1933 	public Address getNextFileAddress() {
1934 		return nextFileAddress;
1935 	}
1936 
1937 	/**
1938 	 * Registers the address of the main input loop's next-record entry point,
1939 	 * so that a runtime {@code next} statement executed from a user-defined
1940 	 * function can resume the loop there. A property of the tuple stream
1941 	 * rather than a tuple: the interpreter reads it once when it installs the
1942 	 * program.
1943 	 *
1944 	 * @param addr address where the main input loop consumes the next record
1945 	 */
1946 	public void setNextAddress(Address addr) {
1947 		nextAddress = addr;
1948 	}
1949 
1950 	/**
1951 	 * Returns the address of the main input loop's next-record entry point,
1952 	 * or {@code null} when the program has no main input loop.
1953 	 *
1954 	 * @return address where the main input loop consumes the next record, or
1955 	 *         {@code null}
1956 	 */
1957 	public Address getNextAddress() {
1958 		return nextAddress;
1959 	}
1960 
1961 	/**
1962 	 * Emits the tuple advancing the main input to the next input file, or
1963 	 * jumping to the given address when no input file remains.
1964 	 *
1965 	 * @param address address to jump to when no more input files remain
1966 	 */
1967 	public void nextFile(Address address) {
1968 		queue.add(new Tuple.AddressTuple(Opcode.NEXT_FILE, address));
1969 	}
1970 
1971 	/**
1972 	 * Emits the tuple consuming one record of the current input file only,
1973 	 * jumping to the given address at end of the current file.
1974 	 *
1975 	 * @param address address to jump to at end of the current input file
1976 	 */
1977 	public void consumeFileInput(Address address) {
1978 		queue.add(new Tuple.AddressTuple(Opcode.CONSUME_FILE_INPUT, address));
1979 	}
1980 
1981 	/**
1982 	 * Emits the tuple executing the {@code nextfile} statement at runtime.
1983 	 */
1984 	public void execNextfile() {
1985 		queue.add(new Tuple.NoOperandTuple(Opcode.EXEC_NEXTFILE));
1986 	}
1987 
1988 	/**
1989 	 * Emits the tuple executing the {@code next} statement at runtime, for
1990 	 * uses inside user-defined functions, where the calling rule cannot be
1991 	 * known statically.
1992 	 */
1993 	public void execNext() {
1994 		queue.add(new Tuple.NoOperandTuple(Opcode.EXEC_NEXT));
1995 	}
1996 
1997 	/**
1998 	 * <p>
1999 	 * exitWithCode.
2000 	 * </p>
2001 	 */
2002 	public void exitWithCode() {
2003 		queue.add(new Tuple.NoOperandTuple(Opcode.EXIT_WITH_CODE));
2004 	}
2005 
2006 	/**
2007 	 * <p>
2008 	 * exitWithCode.
2009 	 * </p>
2010 	 */
2011 	public void exitWithoutCode() {
2012 		queue.add(new Tuple.NoOperandTuple(Opcode.EXIT_WITHOUT_CODE));
2013 	}
2014 
2015 	/**
2016 	 * <p>
2017 	 * regexp.
2018 	 * </p>
2019 	 *
2020 	 * @param regexpStr a {@link java.lang.String} object
2021 	 */
2022 	public void regexp(String regexpStr) {
2023 		// For literal regexes (created by RegexpAst), precompile the Pattern
2024 		// and store it alongside the original string to skip runtime compilation.
2025 		Pattern precompiled = Pattern.compile(regexpStr);
2026 		queue.add(new Tuple.RegexTuple(regexpStr, precompiled));
2027 	}
2028 
2029 	/**
2030 	 * <p>
2031 	 * regexpPair.
2032 	 * </p>
2033 	 *
2034 	 * @deprecated Evaluates both range conditions on every record, which is
2035 	 *             incorrect when the conditions have side effects. Use
2036 	 *             {@link #conditionPairInRange(long)},
2037 	 *             {@link #conditionPairEnter(long)} and
2038 	 *             {@link #conditionPairLeave(long)} with conditional jumps instead.
2039 	 */
2040 	@Deprecated
2041 	public void conditionPair() {
2042 		queue.add(new Tuple.NoOperandTuple(Opcode.CONDITION_PAIR));
2043 	}
2044 
2045 	/**
2046 	 * Pushes whether the specified range pattern is currently active, i.e. its
2047 	 * start condition matched a previous record and its end condition hasn't
2048 	 * matched yet.
2049 	 *
2050 	 * @param id unique identifier of the range pattern within the script
2051 	 */
2052 	public void conditionPairInRange(long id) {
2053 		queue.add(new Tuple.LongTuple(Opcode.CONDITION_PAIR_IN_RANGE, id));
2054 	}
2055 
2056 	/**
2057 	 * Marks the specified range pattern as active, after its start condition
2058 	 * matched the current record.
2059 	 *
2060 	 * @param id unique identifier of the range pattern within the script
2061 	 */
2062 	public void conditionPairEnter(long id) {
2063 		queue.add(new Tuple.LongTuple(Opcode.CONDITION_PAIR_ENTER, id));
2064 	}
2065 
2066 	/**
2067 	 * Marks the specified range pattern as inactive, after its end condition
2068 	 * matched the current record.
2069 	 *
2070 	 * @param id unique identifier of the range pattern within the script
2071 	 */
2072 	public void conditionPairLeave(long id) {
2073 		queue.add(new Tuple.LongTuple(Opcode.CONDITION_PAIR_LEAVE, id));
2074 	}
2075 
2076 	/**
2077 	 * <p>
2078 	 * isIn.
2079 	 * </p>
2080 	 */
2081 	public void isIn() {
2082 		queue.add(new Tuple.NoOperandTuple(Opcode.IS_IN));
2083 	}
2084 
2085 	/**
2086 	 * Emits a tuple that pushes the current script context onto the stack.
2087 	 */
2088 	public void scriptThis() {
2089 		queue.add(new Tuple.NoOperandTuple(Opcode.THIS));
2090 	}
2091 
2092 	/**
2093 	 * Emits an extension invocation tuple.
2094 	 *
2095 	 * @param function metadata describing the extension method to invoke
2096 	 * @param paramCount number of arguments supplied for the call
2097 	 */
2098 	public void extension(ExtensionFunction function, int paramCount) {
2099 		queue.add(new Tuple.ExtensionTuple(function, paramCount));
2100 	}
2101 
2102 	/**
2103 	 * Dumps the queued tuples to the provided {@link PrintStream}.
2104 	 *
2105 	 * @param ps destination stream for the tuple listing
2106 	 */
2107 	public void dump(PrintStream ps) {
2108 		ps.println("(intermediate serialVersionUID = " + serialVersionUID + ")");
2109 		ps.println();
2110 		for (int i = 0; i < queue.size(); i++) {
2111 			Address address = addressManager.getAddress(i);
2112 			if (address == null) {
2113 				ps.println(i + " : " + queue.get(i));
2114 			} else {
2115 				ps.println(i + " : [" + address + "] : " + queue.get(i));
2116 			}
2117 		}
2118 	}
2119 
2120 	/**
2121 	 * <p>
2122 	 * top.
2123 	 * </p>
2124 	 *
2125 	 * @return a {@link io.jawk.intermediate.PositionTracker} object
2126 	 */
2127 	public PositionTracker top() {
2128 		return new PositionTracker(queue);
2129 	}
2130 
2131 	/**
2132 	 * Executed after all tuples are entered in the queue.
2133 	 * Its main functions are:
2134 	 * <ul>
2135 	 * <li>Assign queue.next to the next element in the queue.
2136 	 * <li>Calls touch(...) per Tuple so that addresses can be normalized/assigned/allocated
2137 	 * properly.
2138 	 * </ul>
2139 	 */
2140 	public void postProcess() {
2141 		if (postProcessed) {
2142 			return;
2143 		}
2144 		if (!queue.isEmpty() && queue.get(0).hasNext()) {
2145 			postProcessed = true;
2146 			return;
2147 		}
2148 		assignSequentialNextPointers();
2149 		for (Tuple tuple : queue) {
2150 			tuple.touch(queue);
2151 		}
2152 		postProcessed = true;
2153 	}
2154 
2155 	/**
2156 	 * Performs tuple queue optimizations such as reachability pruning, redundant
2157 	 * eval-global setup removal, and NOP collapsing.
2158 	 * <p>
2159 	 * This method is idempotent. Repeated invocations after a successful
2160 	 * optimization run will have no additional effect.
2161 	 * </p>
2162 	 * <p>
2163 	 * Peephole optimization happens at the tuple layer instead of during AST
2164 	 * construction. Folding after parsing guarantees that any tuple-level
2165 	 * transformations (for example, address resolution and extension hooks) have
2166 	 * already run, and it keeps a single optimization toggle ({@code optimize()})
2167 	 * for callers. Performing the work at the tuple layer also lets us recurse
2168 	 * until no more changes occur without complicating the parser.
2169 	 * </p>
2170 	 */
2171 	public void optimize() {
2172 		if (optimized) {
2173 			return;
2174 		}
2175 		if (!postProcessed) {
2176 			postProcess();
2177 		}
2178 		boolean queueModified = removeRedundantEvalSetNumGlobals();
2179 		queueModified |= peepholeOptimize();
2180 		if (queueModified) {
2181 			reprocessQueue();
2182 		}
2183 		simplifyControlFlow();
2184 		optimizeQueue();
2185 		optimized = true;
2186 	}
2187 
2188 	/**
2189 	 * Removes the synthetic {@code SET_NUM_GLOBALS} prelude from eval tuple
2190 	 * streams that never touch runtime-stack-backed variables or global metadata.
2191 	 * <p>
2192 	 * Expression compilation always emits the opcode up front, but field-only or
2193 	 * JRT-special-only expressions can execute without initializing the AVM global
2194 	 * frame. Dropping the tuple here keeps the runtime path lean while preserving
2195 	 * the parser's simpler tuple construction flow.
2196 	 * </p>
2197 	 *
2198 	 * @return {@code true} when a redundant eval {@code SET_NUM_GLOBALS} tuple was
2199 	 *         removed
2200 	 */
2201 	private boolean removeRedundantEvalSetNumGlobals() {
2202 		int setNumGlobalsIndex = -1;
2203 		for (int i = 0; i < queue.size(); i++) {
2204 			Opcode opcode = queue.get(i).getOpcode();
2205 			if (opcode == null) {
2206 				continue;
2207 			}
2208 			switch (opcode) {
2209 			case SET_NUM_GLOBALS:
2210 				if (setNumGlobalsIndex != -1) {
2211 					return false;
2212 				}
2213 				setNumGlobalsIndex = i;
2214 				break;
2215 			default:
2216 				if (requiresEvalGlobalFrame(opcode)) {
2217 					return false;
2218 				}
2219 				break;
2220 			}
2221 		}
2222 		if (!evalTupleStream || setNumGlobalsIndex < 0) {
2223 			return false;
2224 		}
2225 
2226 		int[] indexMapping = new int[queue.size()];
2227 		for (int i = 0, nextIndex = 0; i < queue.size(); i++) {
2228 			if (i == setNumGlobalsIndex) {
2229 				indexMapping[i] = nextIndex;
2230 			} else {
2231 				indexMapping[i] = nextIndex++;
2232 			}
2233 		}
2234 		queue.remove(setNumGlobalsIndex);
2235 		remapAddresses(indexMapping);
2236 		return true;
2237 	}
2238 
2239 	private boolean peepholeOptimize() {
2240 		// Keep running the local rewrite pass because one fold can expose another.
2241 		// Example: PUSH 1, PUSH 2, ADD, NEGATE first becomes PUSH 3, NEGATE and
2242 		// only the next pass can fold it to PUSH -3.
2243 		boolean modified = false;
2244 		boolean passModified;
2245 		do {
2246 			passModified = peepholeOptimizePass();
2247 			modified |= passModified;
2248 		} while (passModified);
2249 		return modified;
2250 	}
2251 
2252 	private boolean peepholeOptimizePass() {
2253 		int originalSize = queue.size();
2254 		if (originalSize < 2) {
2255 			return false;
2256 		}
2257 
2258 		List<Tuple> original = new ArrayList<Tuple>(queue);
2259 		int[] indexMapping = new int[originalSize];
2260 		Arrays.fill(indexMapping, -1);
2261 		List<Tuple> optimizedQueue = new ArrayList<Tuple>(originalSize);
2262 		boolean[] isAddressTarget = addressTargets(original, originalSize);
2263 
2264 		boolean modified = false;
2265 		int oldIndex = 0;
2266 		int newIndex = 0;
2267 		while (oldIndex < originalSize) {
2268 			Tuple tuple = original.get(oldIndex);
2269 			// If an earlier rewrite already happened in this pass, wait for the
2270 			// next pass before collapsing concat runs. That gives literal folding
2271 			// priority so fully constant chains become one PUSH_STRING instead of a
2272 			// partially folded PUSH_STRING plus MULTI_CONCAT.
2273 			ConcatRun concatRun = !modified ? concatRun(original, isAddressTarget, oldIndex) : null;
2274 			if (concatRun != null) {
2275 				// Chained concatenations compile as a run of binary CONCAT tuples
2276 				// after all operands have been pushed. Collapse that postfix run into
2277 				// one counted MULTI_CONCAT, e.g. CONCAT, CONCAT, CONCAT ->
2278 				// MULTI_CONCAT 4.
2279 				Tuple replacement = createMultiConcat(concatRun.itemCount, tuple.getLineNumber());
2280 				optimizedQueue.add(replacement);
2281 				mapFoldedRange(indexMapping, oldIndex, concatRun.tupleCount, newIndex);
2282 				oldIndex += concatRun.tupleCount;
2283 				newIndex++;
2284 				modified = true;
2285 				continue;
2286 			}
2287 
2288 			if (tuple.getOpcode() == Opcode.ASSIGN && (oldIndex + 1) < originalSize) {
2289 				Tuple nextTuple = original.get(oldIndex + 1);
2290 				// Statement assignments compile as ASSIGN followed by POP because
2291 				// ASSIGN normally leaves the assigned value on the stack for
2292 				// expression contexts such as print (a = 1). When the result is
2293 				// discarded immediately, replace both opcodes with ASSIGN_NOPUSH
2294 				// unless the POP itself is a branch target. Branches that land on
2295 				// the POP must continue to skip the assignment and only discard the
2296 				// already-computed expression result.
2297 				if (nextTuple.getOpcode() == Opcode.POP && !isAddressTarget[oldIndex + 1]) {
2298 					Tuple replacement = createAssignNoPush(tuple);
2299 					optimizedQueue.add(replacement);
2300 					mapFoldedRange(indexMapping, oldIndex, 2, newIndex);
2301 					oldIndex += 2;
2302 					newIndex++;
2303 					modified = true;
2304 					continue;
2305 				}
2306 			}
2307 
2308 			// A fold may only consume tuples no branch jumps to: a jump into the
2309 			// middle of the folded range would be remapped onto the replacement
2310 			// and execute the whole fold, corrupting the operand stack — the join
2311 			// point of a ternary, for example, is exactly such a target. A jump
2312 			// to the first tuple of the range is fine: it lands on the
2313 			// replacement, which computes the same value.
2314 			Object literal = literalValue(tuple);
2315 			if (literal != null) {
2316 				if ((oldIndex + 1) < originalSize && !isAddressTarget[oldIndex + 1]) {
2317 					Tuple nextTuple = original.get(oldIndex + 1);
2318 					if (nextTuple.getOpcode() == Opcode.GET_INPUT_FIELD) {
2319 						// Replace PUSH literal + GET_INPUT_FIELD with the constant-field
2320 						// opcode so $1, $2, etc. do not need a stack round trip for the
2321 						// field index.
2322 						long fieldIndex = JRT.toLong(literal);
2323 						Tuple replacement = createGetInputFieldConst(
2324 								fieldIndex,
2325 								tuple.getLineNumber());
2326 						optimizedQueue.add(replacement);
2327 						mapFoldedRange(indexMapping, oldIndex, 2, newIndex);
2328 						oldIndex += 2;
2329 						newIndex++;
2330 						modified = true;
2331 						continue;
2332 					}
2333 				}
2334 				if ((oldIndex + 2) < originalSize
2335 						&& !isAddressTarget[oldIndex + 1]
2336 						&& !isAddressTarget[oldIndex + 2]) {
2337 					Tuple nextTuple = original.get(oldIndex + 1);
2338 					Tuple opTuple = original.get(oldIndex + 2);
2339 					Object secondLiteral = literalValue(nextTuple);
2340 					if (secondLiteral != null) {
2341 						Object folded = foldBinary(literal, secondLiteral, opTuple);
2342 						if (folded != null) {
2343 							// Fold two literal pushes followed by a pure binary operator
2344 							// into a single literal push, e.g. PUSH 1, PUSH 2, ADD ->
2345 							// PUSH 3.
2346 							Tuple replacement = createLiteralPush(folded, tuple.getLineNumber());
2347 							optimizedQueue.add(replacement);
2348 							mapFoldedRange(indexMapping, oldIndex, 3, newIndex);
2349 							oldIndex += 3;
2350 							newIndex++;
2351 							modified = true;
2352 							continue;
2353 						}
2354 					}
2355 				}
2356 				if ((oldIndex + 1) < originalSize && !isAddressTarget[oldIndex + 1]) {
2357 					Tuple opTuple = original.get(oldIndex + 1);
2358 					Object folded = foldUnary(literal, opTuple);
2359 					if (folded != null) {
2360 						// Fold one literal push followed by a pure unary operator into a
2361 						// single literal push, e.g. PUSH 5, NEGATE -> PUSH -5.
2362 						Tuple replacement = createLiteralPush(folded, tuple.getLineNumber());
2363 						optimizedQueue.add(replacement);
2364 						mapFoldedRange(indexMapping, oldIndex, 2, newIndex);
2365 						oldIndex += 2;
2366 						newIndex++;
2367 						modified = true;
2368 						continue;
2369 					}
2370 				}
2371 			}
2372 
2373 			optimizedQueue.add(tuple);
2374 			indexMapping[oldIndex] = newIndex;
2375 			oldIndex++;
2376 			newIndex++;
2377 		}
2378 
2379 		if (!modified) {
2380 			return false;
2381 		}
2382 
2383 		for (int i = 0; i < optimizedQueue.size(); i++) {
2384 			queue.set(i, optimizedQueue.get(i));
2385 		}
2386 		for (int i = queue.size() - 1; i >= optimizedQueue.size(); i--) {
2387 			queue.remove(i);
2388 		}
2389 
2390 		remapAddresses(indexMapping);
2391 		return true;
2392 	}
2393 
2394 	private boolean[] addressTargets(List<Tuple> tuples, int tupleCount) {
2395 		boolean[] targets = new boolean[tupleCount];
2396 		for (Tuple tuple : tuples) {
2397 			for (Address address : tuple.getAddresses()) {
2398 				int index = address.index();
2399 				if (index >= 0 && index < tupleCount) {
2400 					targets[index] = true;
2401 				}
2402 			}
2403 		}
2404 		return targets;
2405 	}
2406 
2407 	private void mapFoldedRange(int[] indexMapping, int startIndex, int length, int newIndex) {
2408 		for (int idx = 0; idx < length; idx++) {
2409 			indexMapping[startIndex + idx] = newIndex;
2410 		}
2411 	}
2412 
2413 	private ConcatRun concatRun(List<Tuple> original, boolean[] isAddressTarget, int oldIndex) {
2414 		Tuple tuple = original.get(oldIndex);
2415 		if (tuple.getOpcode() != Opcode.CONCAT || isAddressTarget[oldIndex]) {
2416 			return null;
2417 		}
2418 
2419 		int itemCount = 2;
2420 		int tupleCount = 1;
2421 		int currentIndex = oldIndex + 1;
2422 		while (currentIndex < original.size()
2423 				&& original.get(currentIndex).getOpcode() == Opcode.CONCAT
2424 				&& !isAddressTarget[currentIndex]) {
2425 			itemCount++;
2426 			tupleCount++;
2427 			currentIndex++;
2428 		}
2429 
2430 		if (tupleCount < 2) {
2431 			return null;
2432 		}
2433 		return new ConcatRun(tupleCount, itemCount);
2434 	}
2435 
2436 	private Object literalValue(Tuple tuple) {
2437 		switch (tuple.getOpcode()) {
2438 		case PUSH_LONG:
2439 			return Long.valueOf(((Tuple.PushLongTuple) tuple).getValue());
2440 		case PUSH_DOUBLE:
2441 			return Double.valueOf(((Tuple.PushDoubleTuple) tuple).getValue());
2442 		case PUSH_STRING:
2443 			return ((Tuple.PushStringTuple) tuple).getValue();
2444 		default:
2445 			return null;
2446 		}
2447 	}
2448 
2449 	private Object foldBinary(Object left, Object right, Tuple operation) {
2450 		Opcode opcode = operation.getOpcode();
2451 		if (opcode == null) {
2452 			return null;
2453 		}
2454 		// Arithmetic folds through the same JRT helpers the interpreter uses,
2455 		// and keeps their exact result type: normalizing an integral Double
2456 		// to a Long here would give the folded constant an exactness in later
2457 		// folds that the runtime result would not have.
2458 		switch (opcode) {
2459 		case ADD:
2460 			return JRT.add(left, right);
2461 		case SUBTRACT:
2462 			return JRT.subtract(left, right);
2463 		case MULTIPLY:
2464 			return JRT.multiply(left, right);
2465 		case DIVIDE:
2466 			return JRT.divide(left, right);
2467 		case MOD:
2468 			return JRT.mod(left, right);
2469 		case POW:
2470 			return JRT.pow(left, right);
2471 		case CMP_EQ:
2472 		case CMP_LT:
2473 		case CMP_GT:
2474 			// only numeric comparisons are compile-time constants: string
2475 			// comparisons depend on the runtime IGNORECASE setting
2476 			if (!(left instanceof Number) || !(right instanceof Number)) {
2477 				return null;
2478 			}
2479 			return JRT.compare2(left, right, opcode == Opcode.CMP_EQ ? 0 : opcode == Opcode.CMP_LT ? -1 : 1) ?
2480 					Long.valueOf(1L) : Long.valueOf(0L);
2481 		case CONCAT:
2482 			if (left instanceof String && right instanceof String) {
2483 				return ((String) left) + ((String) right);
2484 			}
2485 			return null;
2486 		default:
2487 			return null;
2488 		}
2489 	}
2490 
2491 	private Object foldUnary(Object literal, Tuple operation) {
2492 		Opcode opcode = operation.getOpcode();
2493 		if (opcode == null) {
2494 			return null;
2495 		}
2496 		switch (opcode) {
2497 		case NEGATE:
2498 			return JRT.negate(literal);
2499 		case UNARY_PLUS:
2500 			// The interpreter pushes a numeric scalar back unchanged; only a
2501 			// string literal needs the numeric conversion.
2502 			if (literal instanceof Long || literal instanceof Double) {
2503 				return literal;
2504 			}
2505 			return JRT.toDouble(literal);
2506 		default:
2507 			return null;
2508 		}
2509 	}
2510 
2511 	private Tuple createLiteralPush(Object value, int lineNumber) {
2512 		Tuple tuple;
2513 		if (value instanceof Long) {
2514 			tuple = new Tuple.PushLongTuple(((Long) value).longValue());
2515 		} else if (value instanceof Integer) {
2516 			tuple = new Tuple.PushLongTuple(((Integer) value).longValue());
2517 		} else if (value instanceof Double) {
2518 			tuple = new Tuple.PushDoubleTuple(((Double) value).doubleValue());
2519 		} else if (value instanceof Number) {
2520 			Object scalar = JRT.toScalarNumber(((Number) value).doubleValue());
2521 			if (scalar instanceof Long) {
2522 				tuple = new Tuple.PushLongTuple(((Long) scalar).longValue());
2523 			} else {
2524 				tuple = new Tuple.PushDoubleTuple(((Double) scalar).doubleValue());
2525 			}
2526 		} else if (value instanceof String) {
2527 			tuple = new Tuple.PushStringTuple((String) value);
2528 		} else {
2529 			throw new IllegalArgumentException("Unsupported literal value: " + value);
2530 		}
2531 		tuple.setLineNumber(lineNumber);
2532 		return tuple;
2533 	}
2534 
2535 	private Tuple createAssignNoPush(Tuple tuple) {
2536 		Tuple.VariableTuple variableTuple = (Tuple.VariableTuple) tuple;
2537 		Tuple replacement = new Tuple.VariableTuple(
2538 				Opcode.ASSIGN_NOPUSH,
2539 				variableTuple.getVariableOffset(),
2540 				variableTuple.isGlobal());
2541 		replacement.setLineNumber(tuple.getLineNumber());
2542 		return replacement;
2543 	}
2544 
2545 	private Tuple createGetInputFieldConst(long fieldIndex, int lineNumber) {
2546 		Tuple tuple = new Tuple.InputFieldTuple(fieldIndex);
2547 		tuple.setLineNumber(lineNumber);
2548 		return tuple;
2549 	}
2550 
2551 	private Tuple createMultiConcat(int itemCount, int lineNumber) {
2552 		Tuple tuple = new Tuple.CountTuple(Opcode.MULTI_CONCAT, itemCount);
2553 		tuple.setLineNumber(lineNumber);
2554 		return tuple;
2555 	}
2556 
2557 	private static final class ConcatRun {
2558 		private final int tupleCount;
2559 		private final int itemCount;
2560 
2561 		private ConcatRun(int tupleCount, int itemCount) {
2562 			this.tupleCount = tupleCount;
2563 			this.itemCount = itemCount;
2564 		}
2565 	}
2566 
2567 	private void remapAddresses(int[] indexMapping) {
2568 		if (indexMapping.length == 0) {
2569 			return;
2570 		}
2571 		Set<Address> processedAddresses = Collections.newSetFromMap(new IdentityHashMap<Address, Boolean>());
2572 		for (Tuple tuple : queue) {
2573 			for (Address address : tuple.getAddresses()) {
2574 				remapAddress(address, indexMapping, processedAddresses);
2575 			}
2576 		}
2577 		// Property addresses may not be referenced by any tuple (e.g. after
2578 		// jump threading rewired the loop-back GOTO), so they must be
2579 		// remapped explicitly to stay valid.
2580 		remapAddress(exitAddress, indexMapping, processedAddresses);
2581 		remapAddress(endFileAddress, indexMapping, processedAddresses);
2582 		remapAddress(nextFileAddress, indexMapping, processedAddresses);
2583 		remapAddress(nextAddress, indexMapping, processedAddresses);
2584 		addressManager.remapIndexes(indexMapping);
2585 	}
2586 
2587 	private static void seedPropertyAddress(
2588 			Address address,
2589 			int size,
2590 			boolean[] reachable,
2591 			Deque<Integer> worklist) {
2592 		if (address == null) {
2593 			return;
2594 		}
2595 		int targetIndex = address.index();
2596 		if (targetIndex >= 0 && targetIndex < size && !reachable[targetIndex]) {
2597 			reachable[targetIndex] = true;
2598 			worklist.addLast(targetIndex);
2599 		}
2600 	}
2601 
2602 	private static void remapAddress(Address address, int[] indexMapping, Set<Address> processedAddresses) {
2603 		if (address == null || !processedAddresses.add(address)) {
2604 			return;
2605 		}
2606 		int oldIndex = address.index();
2607 		if (oldIndex >= 0 && oldIndex < indexMapping.length) {
2608 			int mappedIndex = indexMapping[oldIndex];
2609 			if (mappedIndex < 0) {
2610 				throw new Error("Address " + address + " references removed tuple " + oldIndex);
2611 			}
2612 			address.assignIndex(mappedIndex);
2613 		}
2614 	}
2615 
2616 	private void reprocessQueue() {
2617 		assignSequentialNextPointers();
2618 		for (Tuple tuple : queue) {
2619 			tuple.touch(queue);
2620 		}
2621 	}
2622 
2623 	private boolean simplifyControlFlow() {
2624 		boolean modified = false;
2625 		boolean passModified;
2626 		do {
2627 			passModified = simplifyControlFlowPass();
2628 			if (passModified) {
2629 				reprocessQueue();
2630 			}
2631 			modified |= passModified;
2632 		} while (passModified);
2633 		return modified;
2634 	}
2635 
2636 	private boolean simplifyControlFlowPass() {
2637 		int size = queue.size();
2638 		if (size < 2) {
2639 			return false;
2640 		}
2641 
2642 		boolean modified = false;
2643 		boolean[] remove = new boolean[size];
2644 		int[] redirectTargets = new int[size];
2645 		int[] visitStamps = new int[size];
2646 		int nextVisitStamp = 1;
2647 		Arrays.fill(redirectTargets, -1);
2648 
2649 		for (int i = 0; i < size; i++) {
2650 			Tuple tuple = queue.get(i);
2651 			Address address = tuple.getAddress();
2652 			if (address != null) {
2653 				int resolvedTarget = resolveJumpEquivalentIndex(
2654 						address.index(),
2655 						size,
2656 						visitStamps,
2657 						nextVisitStamp++);
2658 				if (resolvedTarget >= 0 && resolvedTarget != address.index()) {
2659 					addressManager.reassignAddress(address, resolvedTarget);
2660 					modified = true;
2661 				}
2662 			}
2663 
2664 			switch (tuple.getOpcode()) {
2665 			case NOP: {
2666 				int redirectTarget = resolveJumpEquivalentIndex(
2667 						i + 1,
2668 						size,
2669 						visitStamps,
2670 						nextVisitStamp++);
2671 				if (redirectTarget >= 0) {
2672 					remove[i] = true;
2673 					redirectTargets[i] = redirectTarget;
2674 					modified = true;
2675 				}
2676 				break;
2677 			}
2678 			case GOTO: {
2679 				int target = resolveJumpEquivalentIndex(
2680 						tuple.getAddress().index(),
2681 						size,
2682 						visitStamps,
2683 						nextVisitStamp++);
2684 				int fallthroughTarget = resolveJumpEquivalentIndex(
2685 						i + 1,
2686 						size,
2687 						visitStamps,
2688 						nextVisitStamp++);
2689 				if (target >= 0 && target == fallthroughTarget) {
2690 					remove[i] = true;
2691 					redirectTargets[i] = fallthroughTarget;
2692 					modified = true;
2693 				}
2694 				break;
2695 			}
2696 			default:
2697 				break;
2698 			}
2699 		}
2700 
2701 		if (!modified) {
2702 			return false;
2703 		}
2704 
2705 		boolean anyRemoved = false;
2706 		for (boolean removeTuple : remove) {
2707 			if (removeTuple) {
2708 				anyRemoved = true;
2709 				break;
2710 			}
2711 		}
2712 		if (!anyRemoved) {
2713 			return true;
2714 		}
2715 
2716 		int[] indexMapping = new int[size];
2717 		Arrays.fill(indexMapping, -1);
2718 		int nextIndex = 0;
2719 		for (int i = 0; i < size; i++) {
2720 			if (!remove[i]) {
2721 				indexMapping[i] = nextIndex++;
2722 			}
2723 		}
2724 		for (int i = 0; i < size; i++) {
2725 			if (remove[i] && redirectTargets[i] >= 0) {
2726 				indexMapping[i] = indexMapping[redirectTargets[i]];
2727 			}
2728 		}
2729 
2730 		compactQueue(remove);
2731 
2732 		remapAddresses(indexMapping);
2733 		return true;
2734 	}
2735 
2736 	private int resolveJumpEquivalentIndex(int index, int size, int[] visitStamps, int stamp) {
2737 		if (index < 0 || index >= size) {
2738 			return -1;
2739 		}
2740 		int current = index;
2741 		while (current >= 0 && current < size && visitStamps[current] != stamp) {
2742 			visitStamps[current] = stamp;
2743 			Tuple tuple = queue.get(current);
2744 			switch (tuple.getOpcode()) {
2745 			case NOP:
2746 				current++;
2747 				break;
2748 			case GOTO: {
2749 				Address address = tuple.getAddress();
2750 				if (address == null) {
2751 					return current;
2752 				}
2753 				current = address.index();
2754 				break;
2755 			}
2756 			default:
2757 				return current;
2758 			}
2759 		}
2760 		return -1;
2761 	}
2762 
2763 	private void assignSequentialNextPointers() {
2764 		for (int i = 0; i < queue.size(); i++) {
2765 			Tuple nextTuple = (i + 1) < queue.size() ? queue.get(i + 1) : null;
2766 			queue.get(i).setNext(nextTuple);
2767 		}
2768 	}
2769 
2770 	private void compactQueue(boolean[] remove) {
2771 		ArrayList<Tuple> compactedQueue = new ArrayList<Tuple>(queue.size());
2772 		for (int i = 0; i < remove.length; i++) {
2773 			if (!remove[i]) {
2774 				compactedQueue.add(queue.get(i));
2775 			}
2776 		}
2777 		queue.clear();
2778 		queue.addAll(compactedQueue);
2779 	}
2780 
2781 	private void optimizeQueue() {
2782 		int size = queue.size();
2783 		if (size <= 1) {
2784 			return;
2785 		}
2786 
2787 		boolean[] reachable = new boolean[size];
2788 		int[] referencesFromReachable = new int[size];
2789 
2790 		Deque<Integer> worklist = new ArrayDeque<>();
2791 		if (!queue.isEmpty()) {
2792 			reachable[0] = true;
2793 			worklist.add(0);
2794 		}
2795 		// The property addresses are runtime jump targets (exit, next,
2796 		// nextfile, and the ENDFILE section nextfile resumes at) that no
2797 		// tuple may reference: treat them as reachability roots so their
2798 		// sections are never eliminated as dead code.
2799 		seedPropertyAddress(exitAddress, size, reachable, worklist);
2800 		seedPropertyAddress(endFileAddress, size, reachable, worklist);
2801 		seedPropertyAddress(nextFileAddress, size, reachable, worklist);
2802 		seedPropertyAddress(nextAddress, size, reachable, worklist);
2803 
2804 		while (!worklist.isEmpty()) {
2805 			int index = worklist.removeFirst();
2806 			Tuple tuple = queue.get(index);
2807 
2808 			if (fallsThrough(tuple.getOpcode())) {
2809 				Tuple nextTuple = tuple.getNext();
2810 				if (nextTuple != null) {
2811 					int nextIndex = index + 1;
2812 					if (!reachable[nextIndex]) {
2813 						reachable[nextIndex] = true;
2814 						worklist.addLast(nextIndex);
2815 					}
2816 				}
2817 			}
2818 
2819 			for (Address address : tuple.getAddresses()) {
2820 				int targetIndex = address.index();
2821 				if (targetIndex < 0 || targetIndex >= size) {
2822 					throw new Error("address " + address + " doesn't resolve to an actual list element");
2823 				}
2824 				referencesFromReachable[targetIndex]++;
2825 				if (!reachable[targetIndex]) {
2826 					reachable[targetIndex] = true;
2827 					worklist.addLast(targetIndex);
2828 				}
2829 			}
2830 		}
2831 
2832 		for (int i = 0; i < size; i++) {
2833 			if (!reachable[i] && referencesFromReachable[i] > 0) {
2834 				reachable[i] = true;
2835 				worklist.addLast(i);
2836 			}
2837 		}
2838 
2839 		while (!worklist.isEmpty()) {
2840 			int index = worklist.removeFirst();
2841 			Tuple tuple = queue.get(index);
2842 
2843 			if (fallsThrough(tuple.getOpcode())) {
2844 				Tuple nextTuple = tuple.getNext();
2845 				if (nextTuple != null) {
2846 					int nextIndex = index + 1;
2847 					if (!reachable[nextIndex]) {
2848 						reachable[nextIndex] = true;
2849 						worklist.addLast(nextIndex);
2850 					}
2851 				}
2852 			}
2853 
2854 			for (Address address : tuple.getAddresses()) {
2855 				int targetIndex = address.index();
2856 				if (targetIndex < 0 || targetIndex >= size) {
2857 					throw new Error("address " + address + " doesn't resolve to an actual list element");
2858 				}
2859 				referencesFromReachable[targetIndex]++;
2860 				if (!reachable[targetIndex]) {
2861 					reachable[targetIndex] = true;
2862 					worklist.addLast(targetIndex);
2863 				}
2864 			}
2865 		}
2866 
2867 		boolean anyRemoved = false;
2868 		boolean[] remove = new boolean[size];
2869 		for (int i = 0; i < size; i++) {
2870 			if (!reachable[i]) {
2871 				remove[i] = true;
2872 				anyRemoved = true;
2873 				continue;
2874 			}
2875 			Tuple tuple = queue.get(i);
2876 			if (tuple.getOpcode() == Opcode.NOP && referencesFromReachable[i] == 0) {
2877 				remove[i] = true;
2878 				anyRemoved = true;
2879 			}
2880 		}
2881 
2882 		if (!anyRemoved) {
2883 			return;
2884 		}
2885 
2886 		int[] indexMapping = new int[size];
2887 		int nextIndex = 0;
2888 		for (int i = 0; i < size; i++) {
2889 			if (remove[i]) {
2890 				indexMapping[i] = -1;
2891 			} else {
2892 				indexMapping[i] = nextIndex++;
2893 			}
2894 		}
2895 
2896 		compactQueue(remove);
2897 
2898 		if (!queue.isEmpty()) {
2899 			assignSequentialNextPointers();
2900 		}
2901 
2902 		remapAddresses(indexMapping);
2903 	}
2904 
2905 	private boolean fallsThrough(Opcode opcode) {
2906 		if (opcode == null) {
2907 			return true;
2908 		}
2909 		switch (opcode) {
2910 		case GOTO:
2911 		case EXIT_WITH_CODE:
2912 		case EXIT_WITHOUT_CODE:
2913 			return false;
2914 		default:
2915 			return true;
2916 		}
2917 	}
2918 
2919 	/** Map of global variables offsets */
2920 	private Map<String, Integer> globalVarOffsetMap = new HashMap<String, Integer>();
2921 
2922 	/** Map of global arrays */
2923 	private Map<String, Boolean> globalVarAarrayMap = new HashMap<String, Boolean>();
2924 
2925 	/** List of user function names */
2926 	private Set<String> functionNames = new HashSet<String>();
2927 
2928 	/** Whether metadata collections are frozen for execution. */
2929 	private boolean metadataFrozen;
2930 
2931 	/**
2932 	 * Accept a {variable_name -&gt; offset} mapping such that global variables can be
2933 	 * assigned while processing name=value and filename command-line arguments.
2934 	 *
2935 	 * @param varname Name of the global variable
2936 	 * @param offset What offset to use for the variable
2937 	 * @param isArray Whether the variable is actually an array
2938 	 */
2939 	public void addGlobalVariableNameToOffsetMapping(String varname, int offset, boolean isArray) {
2940 		ensureMetadataMutable();
2941 		if (globalVarOffsetMap.get(varname) != null) {
2942 			return;
2943 		}
2944 		globalVarOffsetMap.put(varname, offset);
2945 		globalVarAarrayMap.put(varname, isArray);
2946 	}
2947 
2948 	/**
2949 	 * Accept a set of function names from the parser. This is
2950 	 * useful for invalidating name=value assignments from the
2951 	 * command line parameters, either via -v arguments or
2952 	 * passed into ARGV.
2953 	 *
2954 	 * @param names A set of function name strings.
2955 	 */
2956 	public void setFunctionNameSet(Set<String> names) {
2957 		ensureMetadataMutable();
2958 		// setFunctionNameSet is called with a keySet from
2959 		// a HashMap as a parameter, which is Opcode.NOT
2960 		// Serializable. Creating a new HashSet around
2961 		// the parameter resolves the issue.
2962 		// Otherwise, attempting to serialize this
2963 		// object results in a NotSerializableEexception
2964 		// being thrown because of functionNames field
2965 		// being a keyset from a HashMap.
2966 		this.functionNames = new HashSet<String>(names);
2967 	}
2968 
2969 	/**
2970 	 * Freezes the tuple metadata after compilation so execution can reuse the
2971 	 * published maps and sets without creating fresh unmodifiable wrappers.
2972 	 * Repeated calls are ignored.
2973 	 */
2974 	public void freezeMetadata() {
2975 		if (metadataFrozen) {
2976 			return;
2977 		}
2978 		globalVarOffsetMap = freezeMap(globalVarOffsetMap);
2979 		globalVarAarrayMap = freezeMap(globalVarAarrayMap);
2980 		functionNames = freezeSet(functionNames);
2981 		metadataFrozen = true;
2982 	}
2983 
2984 	/**
2985 	 * <p>
2986 	 * getGlobalVariableOffsetMap.
2987 	 * </p>
2988 	 *
2989 	 * @return a {@link java.util.Map} object
2990 	 */
2991 	@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "freezeMetadata() replaces this field with an unmodifiable snapshot before compiled tuples are exposed")
2992 	public Map<String, Integer> getGlobalVariableOffsetMap() {
2993 		return globalVarOffsetMap;
2994 	}
2995 
2996 	/**
2997 	 * <p>
2998 	 * getGlobalVariableAarrayMap.
2999 	 * </p>
3000 	 *
3001 	 * @return a {@link java.util.Map} object
3002 	 */
3003 	@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "freezeMetadata() replaces this field with an unmodifiable snapshot before compiled tuples are exposed")
3004 	public Map<String, Boolean> getGlobalVariableAarrayMap() {
3005 		return globalVarAarrayMap;
3006 	}
3007 
3008 	/**
3009 	 * <p>
3010 	 * getFunctionNameSet.
3011 	 * </p>
3012 	 *
3013 	 * @return a {@link java.util.Set} object
3014 	 */
3015 	@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "freezeMetadata() replaces this field with an unmodifiable snapshot before compiled tuples are exposed")
3016 	public Set<String> getFunctionNameSet() {
3017 		return functionNames;
3018 	}
3019 
3020 	private void ensureMetadataMutable() {
3021 		if (metadataFrozen) {
3022 			throw new IllegalStateException("Tuple metadata is frozen.");
3023 		}
3024 	}
3025 
3026 	private static <K, V> Map<K, V> freezeMap(Map<K, V> map) {
3027 		if (map.isEmpty()) {
3028 			return Collections.emptyMap();
3029 		}
3030 		return Collections.unmodifiableMap(new HashMap<K, V>(map));
3031 	}
3032 
3033 	private static <T> Set<T> freezeSet(Set<T> set) {
3034 		if (set.isEmpty()) {
3035 			return Collections.emptySet();
3036 		}
3037 		return Collections.unmodifiableSet(new HashSet<T>(set));
3038 	}
3039 
3040 	private boolean requiresEvalGlobalFrame(Opcode opcode) {
3041 		switch (opcode) {
3042 		case ASSIGN:
3043 		case ASSIGN_NOPUSH:
3044 		case ASSIGN_ARRAY:
3045 		case DEREFERENCE:
3046 		case PEEK_DEREFERENCE:
3047 		case PUSH_INDIRECT_ARGUMENT:
3048 		case PLUS_EQ:
3049 		case MINUS_EQ:
3050 		case MULT_EQ:
3051 		case DIV_EQ:
3052 		case MOD_EQ:
3053 		case POW_EQ:
3054 		case PLUS_EQ_ARRAY:
3055 		case MINUS_EQ_ARRAY:
3056 		case MULT_EQ_ARRAY:
3057 		case DIV_EQ_ARRAY:
3058 		case MOD_EQ_ARRAY:
3059 		case POW_EQ_ARRAY:
3060 		case CALL_FUNCTION:
3061 		case INDIRECT_CALL:
3062 			// extension calls read globals (e.g. IGNORECASE) and their
3063 			// beforeStart hooks assign gawk-owned arrays
3064 		case EXTENSION:
3065 		case SET_RETURN_RESULT:
3066 		case RETURN_FROM_FUNCTION:
3067 		case MATCH:
3068 		case DELETE_ARRAY_ELEMENT:
3069 		case DELETE_ARRAY:
3070 		case ENVIRON_OFFSET:
3071 		case ARGC_OFFSET:
3072 		case ARGV_OFFSET:
3073 		case ASSIGN_ARGC:
3074 		case PUSH_ARGC:
3075 			return true;
3076 		default:
3077 			return false;
3078 		}
3079 	}
3080 
3081 	/** linenumber stack ... */
3082 	private Deque<Integer> linenoStack = new ArrayDeque<Integer>();
3083 
3084 	/**
3085 	 * Push the current line number onto the line number stack.
3086 	 * This is called by the parser to keep track of the
3087 	 * current source line number. Keeping track of line
3088 	 * numbers this way allows the runtime to report
3089 	 * more meaningful errors by providing source line numbers
3090 	 * within error reports.
3091 	 *
3092 	 * @param lineno The current source line number.
3093 	 */
3094 	public void pushSourceLineNumber(int lineno) {
3095 		linenoStack.push(lineno);
3096 	}
3097 
3098 	/**
3099 	 * <p>
3100 	 * popSourceLineNumber.
3101 	 * </p>
3102 	 *
3103 	 * @param lineno a int
3104 	 */
3105 	public void popSourceLineNumber(int lineno) {
3106 		linenoStack.pop();
3107 	}
3108 
3109 }