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.Serializable;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.function.Supplier;
33  import java.util.regex.Pattern;
34  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
35  import io.jawk.ext.ExtensionFunction;
36  
37  /**
38   * Represents one instruction in the tuple stream produced by {@link AwkTuples}.
39   * Concrete subclasses carry only the operands required by their opcode or opcode
40   * group.
41   *
42   * @author Danny Daglas
43   * @see AwkTuples
44   */
45  public abstract class Tuple implements Serializable {
46  
47  	private static final long serialVersionUID = 8105941219003992817L;
48  	private final Opcode opcode;
49  	private int lineNumber = -1;
50  	private Tuple next = null;
51  
52  	Tuple(Opcode opcode) {
53  		this.opcode = opcode;
54  	}
55  
56  	/**
57  	 * Returns this tuple's opcode.
58  	 *
59  	 * @return opcode executed by the AVM
60  	 */
61  	public final Opcode getOpcode() {
62  		return opcode;
63  	}
64  
65  	/**
66  	 * Returns this tuple's jump/call address, if it has one.
67  	 *
68  	 * @return tuple address, or {@code null}
69  	 */
70  	public Address getAddress() {
71  		return null;
72  	}
73  
74  	/**
75  	 * Returns every jump/call address carried by this tuple.
76  	 *
77  	 * @return tuple addresses, or an empty list
78  	 */
79  	public List<Address> getAddresses() {
80  		Address address = getAddress();
81  		if (address == null) {
82  			return Collections.emptyList();
83  		}
84  		return Collections.singletonList(address);
85  	}
86  
87  	/**
88  	 * Resolves deferred operands and validates resolved addresses.
89  	 *
90  	 * @param queue tuple queue used to validate address targets
91  	 */
92  	public void touch(List<Tuple> queue) {
93  		for (Address address : getAddresses()) {
94  			if (address.index() == -1) {
95  				throw new Error("address " + address + " is unresolved");
96  			}
97  			if (address.index() >= queue.size()) {
98  				throw new Error("address " + address + " doesn't resolve to an actual list element");
99  			}
100 		}
101 	}
102 
103 	boolean hasNext() {
104 		return next != null;
105 	}
106 
107 	/**
108 	 * Returns the next tuple in execution order.
109 	 *
110 	 * @return next tuple, or {@code null} at the end of the stream
111 	 */
112 	Tuple getNext() {
113 		return next;
114 	}
115 
116 	void setNext(Tuple next) {
117 		this.next = next;
118 	}
119 
120 	void setLineNumber(int lineNumber) {
121 		this.lineNumber = lineNumber;
122 	}
123 
124 	/**
125 	 * Returns the source line number associated with this tuple.
126 	 *
127 	 * @return source line number, or {@code -1} when unknown
128 	 */
129 	public int getLineNumber() {
130 		return lineNumber;
131 	}
132 
133 	private static String stringArgument(String value) {
134 		return ", \"" + value + '"';
135 	}
136 
137 	private static String patternArgument(Pattern pattern) {
138 		return ", /" + (pattern == null ? "" : pattern.pattern()) + '/';
139 	}
140 
141 	/**
142 	 * Tuple for opcodes without operands.
143 	 */
144 	public static class NoOperandTuple extends Tuple {
145 		private static final long serialVersionUID = 1L;
146 
147 		NoOperandTuple(Opcode opcode) {
148 			super(opcode);
149 		}
150 
151 		@Override
152 		public String toString() {
153 			return getOpcode().name();
154 		}
155 	}
156 
157 	/**
158 	 * Tuple for discarding an expression-statement value after validating that it
159 	 * is scalar.
160 	 */
161 	public static final class ScalarPopTuple extends NoOperandTuple {
162 		private static final long serialVersionUID = 1L;
163 
164 		ScalarPopTuple() {
165 			super(Opcode.POP);
166 		}
167 	}
168 
169 	/**
170 	 * Tuple for JRT-managed built-in variable operations.
171 	 */
172 	public static final class BuiltinVarTuple extends NoOperandTuple {
173 		private static final long serialVersionUID = 1L;
174 
175 		BuiltinVarTuple(Opcode opcode) {
176 			super(opcode);
177 		}
178 	}
179 
180 	/**
181 	 * Tuple for a long literal.
182 	 */
183 	public static final class PushLongTuple extends Tuple {
184 		private static final long serialVersionUID = 1L;
185 		private final long value;
186 
187 		PushLongTuple(long value) {
188 			super(Opcode.PUSH_LONG);
189 			this.value = value;
190 		}
191 
192 		/**
193 		 * Returns the literal value.
194 		 *
195 		 * @return literal long value
196 		 */
197 		public long getValue() {
198 			return value;
199 		}
200 
201 		@Override
202 		public String toString() {
203 			return getOpcode().name() + ", " + value;
204 		}
205 	}
206 
207 	/**
208 	 * Tuple for a double literal.
209 	 */
210 	public static final class PushDoubleTuple extends Tuple {
211 		private static final long serialVersionUID = 1L;
212 		private final double value;
213 
214 		PushDoubleTuple(double value) {
215 			super(Opcode.PUSH_DOUBLE);
216 			this.value = value;
217 		}
218 
219 		/**
220 		 * Returns the literal value.
221 		 *
222 		 * @return literal double value
223 		 */
224 		public double getValue() {
225 			return value;
226 		}
227 
228 		@Override
229 		public String toString() {
230 			return getOpcode().name() + ", " + value;
231 		}
232 	}
233 
234 	/**
235 	 * Tuple for a string literal.
236 	 */
237 	public static final class PushStringTuple extends Tuple {
238 		private static final long serialVersionUID = 1L;
239 		private final String value;
240 
241 		PushStringTuple(String value) {
242 			super(Opcode.PUSH_STRING);
243 			this.value = value;
244 		}
245 
246 		/**
247 		 * Returns the literal value.
248 		 *
249 		 * @return literal string value
250 		 */
251 		public String getValue() {
252 			return value;
253 		}
254 
255 		@Override
256 		public String toString() {
257 			return getOpcode().name() + stringArgument(value);
258 		}
259 	}
260 
261 	/**
262 	 * Tuple for opcodes whose single operand is a count.
263 	 */
264 	public static class CountTuple extends Tuple {
265 		private static final long serialVersionUID = 1L;
266 		private final long count;
267 
268 		CountTuple(Opcode opcode, long count) {
269 			super(opcode);
270 			this.count = count;
271 		}
272 
273 		/**
274 		 * Returns the tuple count operand.
275 		 *
276 		 * @return count operand
277 		 */
278 		public final long getCount() {
279 			return count;
280 		}
281 
282 		@Override
283 		public String toString() {
284 			return getOpcode().name() + ", " + count;
285 		}
286 	}
287 
288 	/**
289 	 * Tuple for print/printf redirection with an append flag.
290 	 */
291 	public static final class CountAndAppendTuple extends CountTuple {
292 		private static final long serialVersionUID = 1L;
293 		private final boolean append;
294 
295 		CountAndAppendTuple(Opcode opcode, long count, boolean append) {
296 			super(opcode, count);
297 			this.append = append;
298 		}
299 
300 		/**
301 		 * Indicates whether redirected output should append.
302 		 *
303 		 * @return {@code true} for append mode
304 		 */
305 		public boolean isAppend() {
306 			return append;
307 		}
308 
309 		@Override
310 		public String toString() {
311 			return getOpcode().name() + ", " + getCount() + ", " + append;
312 		}
313 	}
314 
315 	/**
316 	 * Tuple for a long operand that is not interpreted by the tuple itself.
317 	 */
318 	public static class LongTuple extends Tuple {
319 		private static final long serialVersionUID = 1L;
320 		private final long value;
321 
322 		LongTuple(Opcode opcode, long value) {
323 			super(opcode);
324 			this.value = value;
325 		}
326 
327 		/**
328 		 * Returns the long operand.
329 		 *
330 		 * @return long operand
331 		 */
332 		public final long getValue() {
333 			return value;
334 		}
335 
336 		@Override
337 		public String toString() {
338 			return getOpcode().name() + ", " + value;
339 		}
340 	}
341 
342 	/**
343 	 * Tuple for a constant input-field index.
344 	 */
345 	public static final class InputFieldTuple extends LongTuple {
346 		private static final long serialVersionUID = 1L;
347 
348 		InputFieldTuple(long fieldIndex) {
349 			super(Opcode.GET_INPUT_FIELD_CONST, fieldIndex);
350 		}
351 
352 		/**
353 		 * Returns the constant input-field index.
354 		 *
355 		 * @return input-field index
356 		 */
357 		public long getFieldIndex() {
358 			return getValue();
359 		}
360 	}
361 
362 	/**
363 	 * Tuple for an address operand.
364 	 */
365 	public static class AddressTuple extends Tuple {
366 		private static final long serialVersionUID = 1L;
367 		private Address address;
368 
369 		AddressTuple(Opcode opcode, Address address) {
370 			super(opcode);
371 			this.address = address;
372 		}
373 
374 		@Override
375 		public Address getAddress() {
376 			return address;
377 		}
378 
379 		void setAddress(Address address) {
380 			this.address = address;
381 		}
382 
383 		@Override
384 		public String toString() {
385 			return getOpcode().name() + ", " + address;
386 		}
387 	}
388 
389 	/**
390 	 * Tuple for variable offset/global operands.
391 	 */
392 	public static class VariableTuple extends Tuple {
393 		private static final long serialVersionUID = 1L;
394 		private final long variableOffset;
395 		private final boolean global;
396 
397 		VariableTuple(Opcode opcode, long variableOffset, boolean global) {
398 			super(opcode);
399 			this.variableOffset = variableOffset;
400 			this.global = global;
401 		}
402 
403 		/**
404 		 * Returns the variable offset.
405 		 *
406 		 * @return variable offset
407 		 */
408 		public final long getVariableOffset() {
409 			return variableOffset;
410 		}
411 
412 		/**
413 		 * Indicates whether the variable offset belongs to the global frame.
414 		 *
415 		 * @return {@code true} for a global variable
416 		 */
417 		public final boolean isGlobal() {
418 			return global;
419 		}
420 
421 		@Override
422 		public String toString() {
423 			return getOpcode().name() + ", " + variableOffset + ", " + global;
424 		}
425 	}
426 
427 	/**
428 	 * Tuple for scalar compound assignments.
429 	 */
430 	public static final class CompoundAssignTuple extends VariableTuple {
431 		private static final long serialVersionUID = 1L;
432 
433 		CompoundAssignTuple(Opcode opcode, long variableOffset, boolean global) {
434 			super(opcode, variableOffset, global);
435 		}
436 	}
437 
438 	/**
439 	 * Tuple for array compound assignments.
440 	 */
441 	public static final class CompoundAssignArrayTuple extends VariableTuple {
442 		private static final long serialVersionUID = 1L;
443 
444 		CompoundAssignArrayTuple(Opcode opcode, long variableOffset, boolean global) {
445 			super(opcode, variableOffset, global);
446 		}
447 	}
448 
449 	/**
450 	 * Tuple for stack-provided map element compound assignments.
451 	 */
452 	public static final class CompoundAssignMapElementTuple extends NoOperandTuple {
453 		private static final long serialVersionUID = 1L;
454 
455 		CompoundAssignMapElementTuple(Opcode opcode) {
456 			super(opcode);
457 		}
458 	}
459 
460 	/**
461 	 * Tuple for input-field compound assignments.
462 	 */
463 	public static final class CompoundAssignInputFieldTuple extends NoOperandTuple {
464 		private static final long serialVersionUID = 1L;
465 
466 		CompoundAssignInputFieldTuple(Opcode opcode) {
467 			super(opcode);
468 		}
469 	}
470 
471 	/**
472 	 * Tuple for variable dereference.
473 	 */
474 	public static final class DereferenceTuple extends Tuple {
475 		private static final long serialVersionUID = 1L;
476 		private final long variableOffset;
477 		private final boolean array;
478 		private final boolean global;
479 
480 		DereferenceTuple(long variableOffset, boolean array, boolean global) {
481 			super(Opcode.DEREFERENCE);
482 			this.variableOffset = variableOffset;
483 			this.array = array;
484 			this.global = global;
485 		}
486 
487 		/**
488 		 * Returns the variable offset.
489 		 *
490 		 * @return variable offset
491 		 */
492 		public long getVariableOffset() {
493 			return variableOffset;
494 		}
495 
496 		/**
497 		 * Indicates whether this dereference should initialize an array.
498 		 *
499 		 * @return {@code true} when the variable is an array
500 		 */
501 		public boolean isArray() {
502 			return array;
503 		}
504 
505 		/**
506 		 * Indicates whether the variable offset belongs to the global frame.
507 		 *
508 		 * @return {@code true} for a global variable
509 		 */
510 		public boolean isGlobal() {
511 			return global;
512 		}
513 
514 		@Override
515 		public String toString() {
516 			return getOpcode().name() + ", " + variableOffset + ", " + array + ", " + global;
517 		}
518 	}
519 
520 	/**
521 	 * Tuple for boolean operands.
522 	 */
523 	public static final class BooleanTuple extends Tuple {
524 		private static final long serialVersionUID = 1L;
525 		private final boolean value;
526 
527 		BooleanTuple(Opcode opcode, boolean value) {
528 			super(opcode);
529 			this.value = value;
530 		}
531 
532 		/**
533 		 * Returns the boolean operand.
534 		 *
535 		 * @return boolean operand
536 		 */
537 		public boolean getValue() {
538 			return value;
539 		}
540 
541 		@Override
542 		public String toString() {
543 			return getOpcode().name() + ", " + value;
544 		}
545 	}
546 
547 	/**
548 	 * Tuple for sub/gsub against variable-backed values.
549 	 */
550 	public static final class SubstitutionVariableTuple extends VariableTuple {
551 		private static final long serialVersionUID = 1L;
552 		private final boolean globalSubstitution;
553 
554 		SubstitutionVariableTuple(Opcode opcode, long variableOffset, boolean global, boolean globalSubstitution) {
555 			super(opcode, variableOffset, global);
556 			this.globalSubstitution = globalSubstitution;
557 		}
558 
559 		/**
560 		 * Indicates whether this substitution is global.
561 		 *
562 		 * @return {@code true} for {@code gsub}, {@code false} for {@code sub}
563 		 */
564 		public boolean isGlobalSubstitution() {
565 			return globalSubstitution;
566 		}
567 
568 		@Override
569 		public String toString() {
570 			return getOpcode().name() + ", " + getVariableOffset() + ", " + isGlobal() + ", " + globalSubstitution;
571 		}
572 	}
573 
574 	/**
575 	 * Tuple for a precompiled literal regular expression.
576 	 */
577 	public static final class RegexTuple extends Tuple {
578 		private static final long serialVersionUID = 1L;
579 		private final String regex;
580 		private final Pattern pattern;
581 
582 		RegexTuple(String regex, Pattern pattern) {
583 			super(Opcode.REGEXP);
584 			this.regex = regex;
585 			this.pattern = pattern;
586 		}
587 
588 		/**
589 		 * Returns the original regular expression text.
590 		 *
591 		 * @return regular expression text
592 		 */
593 		public String getRegex() {
594 			return regex;
595 		}
596 
597 		/**
598 		 * Returns the precompiled regular expression.
599 		 *
600 		 * @return compiled pattern
601 		 */
602 		public Pattern getPattern() {
603 			return pattern;
604 		}
605 
606 		@Override
607 		public String toString() {
608 			return getOpcode().name() + stringArgument(regex) + patternArgument(pattern);
609 		}
610 	}
611 
612 	/**
613 	 * Tuple for a class check.
614 	 */
615 	public static final class ClassTuple extends Tuple {
616 		private static final long serialVersionUID = 1L;
617 		private final Class<?> type;
618 
619 		ClassTuple(Class<?> type) {
620 			super(Opcode.CHECK_CLASS);
621 			this.type = type;
622 		}
623 
624 		/**
625 		 * Returns the required runtime type.
626 		 *
627 		 * @return required class
628 		 */
629 		public Class<?> getType() {
630 			return type;
631 		}
632 
633 		@Override
634 		public String toString() {
635 			return getOpcode().name() + ", " + type;
636 		}
637 	}
638 
639 	/**
640 	 * Tuple for function definitions.
641 	 */
642 	public static final class FunctionTuple extends Tuple {
643 		private static final long serialVersionUID = 1L;
644 		private final String functionName;
645 		private final long numFormalParams;
646 
647 		FunctionTuple(String functionName, long numFormalParams) {
648 			super(Opcode.FUNCTION);
649 			this.functionName = functionName;
650 			this.numFormalParams = numFormalParams;
651 		}
652 
653 		/**
654 		 * Returns the function name.
655 		 *
656 		 * @return function name
657 		 */
658 		public String getFunctionName() {
659 			return functionName;
660 		}
661 
662 		/**
663 		 * Returns the number of formal parameters.
664 		 *
665 		 * @return formal parameter count
666 		 */
667 		public long getNumFormalParams() {
668 			return numFormalParams;
669 		}
670 
671 		@Override
672 		public String toString() {
673 			return getOpcode().name() + stringArgument(functionName) + ", " + numFormalParams;
674 		}
675 	}
676 
677 	/**
678 	 * Tuple for function calls.
679 	 */
680 	public static final class CallFunctionTuple extends AddressTuple {
681 		private static final long serialVersionUID = 1L;
682 		private transient Supplier<Address> addressSupplier;
683 		private final String functionName;
684 		private final long numFormalParams;
685 		private final long numActualParams;
686 
687 		CallFunctionTuple(
688 				Supplier<Address> addressSupplier,
689 				String functionName,
690 				long numFormalParams,
691 				long numActualParams) {
692 			super(Opcode.CALL_FUNCTION, null);
693 			this.addressSupplier = addressSupplier;
694 			this.functionName = functionName;
695 			this.numFormalParams = numFormalParams;
696 			this.numActualParams = numActualParams;
697 		}
698 
699 		@Override
700 		public Address getAddress() {
701 			Address address = super.getAddress();
702 			if (address == null && addressSupplier != null) {
703 				address = addressSupplier.get();
704 				setAddress(address);
705 				addressSupplier = null;
706 			}
707 			return address;
708 		}
709 
710 		@Override
711 		public void touch(List<Tuple> queue) {
712 			getAddress();
713 			super.touch(queue);
714 		}
715 
716 		/**
717 		 * Returns the function name.
718 		 *
719 		 * @return function name
720 		 */
721 		public String getFunctionName() {
722 			return functionName;
723 		}
724 
725 		/**
726 		 * Returns the number of formal parameters.
727 		 *
728 		 * @return formal parameter count
729 		 */
730 		public long getNumFormalParams() {
731 			return numFormalParams;
732 		}
733 
734 		/**
735 		 * Returns the number of actual parameters at this call site.
736 		 *
737 		 * @return actual parameter count
738 		 */
739 		public long getNumActualParams() {
740 			return numActualParams;
741 		}
742 
743 		@Override
744 		public String toString() {
745 			return getOpcode().name()
746 					+ ", "
747 					+ getAddress()
748 					+ stringArgument(functionName)
749 					+ ", "
750 					+ numFormalParams
751 					+ ", "
752 					+ numActualParams;
753 		}
754 	}
755 
756 	/**
757 	 * Runtime target metadata for a user-defined indirect function call.
758 	 */
759 	public static final class IndirectFunctionTarget implements Serializable {
760 		private static final long serialVersionUID = 1L;
761 		private transient Supplier<Address> addressSupplier;
762 		private Address address;
763 		private final long numFormalParams;
764 		private final Set<Integer> arrayParameterIndexes;
765 
766 		/**
767 		 * Creates target metadata whose address is resolved during tuple
768 		 * post-processing.
769 		 *
770 		 * @param addressSupplierParam function entry-point supplier
771 		 * @param numFormalParamsParam formal parameter count
772 		 * @param arrayParameterIndexesParam zero-based array parameter indexes
773 		 */
774 		public IndirectFunctionTarget(
775 				Supplier<Address> addressSupplierParam,
776 				long numFormalParamsParam,
777 				Set<Integer> arrayParameterIndexesParam) {
778 			addressSupplier = addressSupplierParam;
779 			numFormalParams = numFormalParamsParam;
780 			arrayParameterIndexes = Collections.unmodifiableSet(new HashSet<Integer>(arrayParameterIndexesParam));
781 		}
782 
783 		private void resolve() {
784 			if (address == null && addressSupplier != null) {
785 				address = addressSupplier.get();
786 				addressSupplier = null;
787 			}
788 		}
789 
790 		/**
791 		 * Returns the resolved function entry point.
792 		 *
793 		 * @return function address
794 		 */
795 		public Address getAddress() {
796 			resolve();
797 			return address;
798 		}
799 
800 		/**
801 		 * Returns the function's formal parameter count.
802 		 *
803 		 * @return formal parameter count
804 		 */
805 		public long getNumFormalParams() {
806 			return numFormalParams;
807 		}
808 
809 		/**
810 		 * Returns whether the parameter at the supplied index is an array.
811 		 *
812 		 * @param index zero-based formal parameter index
813 		 * @return {@code true} when the formal parameter is an array
814 		 */
815 		public boolean isArrayParameter(int index) {
816 			return arrayParameterIndexes.contains(Integer.valueOf(index));
817 		}
818 	}
819 
820 	/**
821 	 * Tuple for a function call whose target name is evaluated at runtime.
822 	 */
823 	public static final class IndirectCallTuple extends Tuple {
824 		private static final long serialVersionUID = 1L;
825 		private final Map<String, IndirectFunctionTarget> userFunctions;
826 		private final Map<String, ExtensionFunction> extensionFunctions;
827 		private final long numActualParams;
828 		private final String sourceName;
829 		private final int sourceLine;
830 
831 		IndirectCallTuple(
832 				Map<String, IndirectFunctionTarget> userFunctionsParam,
833 				Map<String, ExtensionFunction> extensionFunctionsParam,
834 				long numActualParamsParam,
835 				String sourceNameParam,
836 				int sourceLineParam) {
837 			super(Opcode.INDIRECT_CALL);
838 			userFunctions = userFunctionsParam;
839 			extensionFunctions = extensionFunctionsParam;
840 			numActualParams = numActualParamsParam;
841 			sourceName = sourceNameParam;
842 			sourceLine = sourceLineParam;
843 		}
844 
845 		@Override
846 		public void touch(List<Tuple> queue) {
847 			for (IndirectFunctionTarget target : userFunctions.values()) {
848 				target.resolve();
849 			}
850 			super.touch(queue);
851 		}
852 
853 		@Override
854 		public List<Address> getAddresses() {
855 			List<Address> addresses = new ArrayList<Address>(userFunctions.size());
856 			for (IndirectFunctionTarget target : userFunctions.values()) {
857 				addresses.add(target.getAddress());
858 			}
859 			return addresses;
860 		}
861 
862 		/**
863 		 * Returns the user-defined functions available to the call site.
864 		 *
865 		 * @return function target map
866 		 */
867 		@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The map is an immutable metadata snapshot shared by every indirect call tuple")
868 		public Map<String, IndirectFunctionTarget> getUserFunctions() {
869 			return userFunctions;
870 		}
871 
872 		/**
873 		 * Returns the extension functions available to the call site.
874 		 *
875 		 * @return extension function map
876 		 */
877 		@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The map is an immutable metadata snapshot shared by every indirect call tuple")
878 		public Map<String, ExtensionFunction> getExtensionFunctions() {
879 			return extensionFunctions;
880 		}
881 
882 		/**
883 		 * Returns the number of actual parameters evaluated by the call site.
884 		 *
885 		 * @return actual parameter count
886 		 */
887 		public long getNumActualParams() {
888 			return numActualParams;
889 		}
890 
891 		/**
892 		 * Returns the source name for runtime diagnostics.
893 		 *
894 		 * @return source name
895 		 */
896 		public String getSourceName() {
897 			return sourceName;
898 		}
899 
900 		/**
901 		 * Returns the source line for runtime diagnostics.
902 		 *
903 		 * @return source line
904 		 */
905 		public int getSourceLine() {
906 			return sourceLine;
907 		}
908 
909 		@Override
910 		public String toString() {
911 			return getOpcode().name() + ", " + numActualParams;
912 		}
913 	}
914 
915 	/**
916 	 * Tuple for extension function invocations.
917 	 */
918 	public static final class ExtensionTuple extends Tuple {
919 		private static final long serialVersionUID = 1L;
920 		private final ExtensionFunction function;
921 		private final long argCount;
922 		private final boolean initial;
923 
924 		ExtensionTuple(ExtensionFunction function, long argCount, boolean initial) {
925 			super(Opcode.EXTENSION);
926 			this.function = function;
927 			this.argCount = argCount;
928 			this.initial = initial;
929 		}
930 
931 		/**
932 		 * Returns the extension function metadata.
933 		 *
934 		 * @return extension function
935 		 */
936 		public ExtensionFunction getFunction() {
937 			return function;
938 		}
939 
940 		/**
941 		 * Returns the number of extension arguments.
942 		 *
943 		 * @return argument count
944 		 */
945 		public long getArgCount() {
946 			return argCount;
947 		}
948 
949 		/**
950 		 * Indicates whether this tuple starts an extension call sequence.
951 		 *
952 		 * @return {@code true} for the initial extension call tuple
953 		 */
954 		public boolean isInitial() {
955 			return initial;
956 		}
957 
958 		@Override
959 		public String toString() {
960 			return getOpcode().name()
961 					+ ", "
962 					+ function.getKeyword()
963 					+ ", "
964 					+ argCount
965 					+ ", "
966 					+ initial;
967 		}
968 	}
969 
970 	/**
971 	 * Tuple carrying a diagnostic message that the interpreter prints to the
972 	 * warning stream when executed. The parser plants these before the
973 	 * instruction they describe, so warnings appear in runtime order, exactly
974 	 * where gawk would emit them.
975 	 */
976 	public static final class WarningTuple extends Tuple {
977 		private static final long serialVersionUID = 1L;
978 		private final String message;
979 
980 		WarningTuple(String message) {
981 			super(Opcode.WARNING);
982 			this.message = message;
983 		}
984 
985 		/**
986 		 * Returns the warning message to print.
987 		 *
988 		 * @return warning text
989 		 */
990 		public String getMessage() {
991 			return message;
992 		}
993 
994 		@Override
995 		public String toString() {
996 			return getOpcode().name() + stringArgument(message);
997 		}
998 	}
999 }