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