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.List;
27  import java.util.function.Supplier;
28  import java.util.regex.Pattern;
29  import io.jawk.ext.ExtensionFunction;
30  
31  /**
32   * Represents one instruction in the tuple stream produced by {@link AwkTuples}.
33   * Concrete subclasses carry only the operands required by their opcode or opcode
34   * group.
35   *
36   * @author Danny Daglas
37   * @see AwkTuples
38   */
39  public abstract class Tuple implements Serializable {
40  
41  	private static final long serialVersionUID = 8105941219003992817L;
42  	private final Opcode opcode;
43  	private int lineNumber = -1;
44  	private Tuple next = null;
45  
46  	Tuple(Opcode opcode) {
47  		this.opcode = opcode;
48  	}
49  
50  	/**
51  	 * Returns this tuple's opcode.
52  	 *
53  	 * @return opcode executed by the AVM
54  	 */
55  	public final Opcode getOpcode() {
56  		return opcode;
57  	}
58  
59  	/**
60  	 * Returns this tuple's jump/call address, if it has one.
61  	 *
62  	 * @return tuple address, or {@code null}
63  	 */
64  	public Address getAddress() {
65  		return null;
66  	}
67  
68  	/**
69  	 * Resolves deferred operands and validates resolved addresses.
70  	 *
71  	 * @param queue tuple queue used to validate address targets
72  	 */
73  	public void touch(List<Tuple> queue) {
74  		Address address = getAddress();
75  		if (address == null) {
76  			return;
77  		}
78  		if (address.index() == -1) {
79  			throw new Error("address " + address + " is unresolved");
80  		}
81  		if (address.index() >= queue.size()) {
82  			throw new Error("address " + address + " doesn't resolve to an actual list element");
83  		}
84  	}
85  
86  	boolean hasNext() {
87  		return next != null;
88  	}
89  
90  	/**
91  	 * Returns the next tuple in execution order.
92  	 *
93  	 * @return next tuple, or {@code null} at the end of the stream
94  	 */
95  	Tuple getNext() {
96  		return next;
97  	}
98  
99  	void setNext(Tuple next) {
100 		this.next = next;
101 	}
102 
103 	void setLineNumber(int lineNumber) {
104 		this.lineNumber = lineNumber;
105 	}
106 
107 	/**
108 	 * Returns the source line number associated with this tuple.
109 	 *
110 	 * @return source line number, or {@code -1} when unknown
111 	 */
112 	public int getLineNumber() {
113 		return lineNumber;
114 	}
115 
116 	private static String stringArgument(String value) {
117 		return ", \"" + value + '"';
118 	}
119 
120 	private static String patternArgument(Pattern pattern) {
121 		return ", /" + (pattern == null ? "" : pattern.pattern()) + '/';
122 	}
123 
124 	/**
125 	 * Tuple for opcodes without operands.
126 	 */
127 	public static class NoOperandTuple extends Tuple {
128 		private static final long serialVersionUID = 1L;
129 
130 		NoOperandTuple(Opcode opcode) {
131 			super(opcode);
132 		}
133 
134 		@Override
135 		public String toString() {
136 			return getOpcode().name();
137 		}
138 	}
139 
140 	/**
141 	 * Tuple for JRT-managed built-in variable operations.
142 	 */
143 	public static final class BuiltinVarTuple extends NoOperandTuple {
144 		private static final long serialVersionUID = 1L;
145 
146 		BuiltinVarTuple(Opcode opcode) {
147 			super(opcode);
148 		}
149 	}
150 
151 	/**
152 	 * Tuple for a long literal.
153 	 */
154 	public static final class PushLongTuple extends Tuple {
155 		private static final long serialVersionUID = 1L;
156 		private final long value;
157 
158 		PushLongTuple(long value) {
159 			super(Opcode.PUSH_LONG);
160 			this.value = value;
161 		}
162 
163 		/**
164 		 * Returns the literal value.
165 		 *
166 		 * @return literal long value
167 		 */
168 		public long getValue() {
169 			return value;
170 		}
171 
172 		@Override
173 		public String toString() {
174 			return getOpcode().name() + ", " + value;
175 		}
176 	}
177 
178 	/**
179 	 * Tuple for a double literal.
180 	 */
181 	public static final class PushDoubleTuple extends Tuple {
182 		private static final long serialVersionUID = 1L;
183 		private final double value;
184 
185 		PushDoubleTuple(double value) {
186 			super(Opcode.PUSH_DOUBLE);
187 			this.value = value;
188 		}
189 
190 		/**
191 		 * Returns the literal value.
192 		 *
193 		 * @return literal double value
194 		 */
195 		public double getValue() {
196 			return value;
197 		}
198 
199 		@Override
200 		public String toString() {
201 			return getOpcode().name() + ", " + value;
202 		}
203 	}
204 
205 	/**
206 	 * Tuple for a string literal.
207 	 */
208 	public static final class PushStringTuple extends Tuple {
209 		private static final long serialVersionUID = 1L;
210 		private final String value;
211 
212 		PushStringTuple(String value) {
213 			super(Opcode.PUSH_STRING);
214 			this.value = value;
215 		}
216 
217 		/**
218 		 * Returns the literal value.
219 		 *
220 		 * @return literal string value
221 		 */
222 		public String getValue() {
223 			return value;
224 		}
225 
226 		@Override
227 		public String toString() {
228 			return getOpcode().name() + stringArgument(value);
229 		}
230 	}
231 
232 	/**
233 	 * Tuple for opcodes whose single operand is a count.
234 	 */
235 	public static class CountTuple extends Tuple {
236 		private static final long serialVersionUID = 1L;
237 		private final long count;
238 
239 		CountTuple(Opcode opcode, long count) {
240 			super(opcode);
241 			this.count = count;
242 		}
243 
244 		/**
245 		 * Returns the tuple count operand.
246 		 *
247 		 * @return count operand
248 		 */
249 		public final long getCount() {
250 			return count;
251 		}
252 
253 		@Override
254 		public String toString() {
255 			return getOpcode().name() + ", " + count;
256 		}
257 	}
258 
259 	/**
260 	 * Tuple for print/printf redirection with an append flag.
261 	 */
262 	public static final class CountAndAppendTuple extends CountTuple {
263 		private static final long serialVersionUID = 1L;
264 		private final boolean append;
265 
266 		CountAndAppendTuple(Opcode opcode, long count, boolean append) {
267 			super(opcode, count);
268 			this.append = append;
269 		}
270 
271 		/**
272 		 * Indicates whether redirected output should append.
273 		 *
274 		 * @return {@code true} for append mode
275 		 */
276 		public boolean isAppend() {
277 			return append;
278 		}
279 
280 		@Override
281 		public String toString() {
282 			return getOpcode().name() + ", " + getCount() + ", " + append;
283 		}
284 	}
285 
286 	/**
287 	 * Tuple for a long operand that is not interpreted by the tuple itself.
288 	 */
289 	public static class LongTuple extends Tuple {
290 		private static final long serialVersionUID = 1L;
291 		private final long value;
292 
293 		LongTuple(Opcode opcode, long value) {
294 			super(opcode);
295 			this.value = value;
296 		}
297 
298 		/**
299 		 * Returns the long operand.
300 		 *
301 		 * @return long operand
302 		 */
303 		public final long getValue() {
304 			return value;
305 		}
306 
307 		@Override
308 		public String toString() {
309 			return getOpcode().name() + ", " + value;
310 		}
311 	}
312 
313 	/**
314 	 * Tuple for a constant input-field index.
315 	 */
316 	public static final class InputFieldTuple extends LongTuple {
317 		private static final long serialVersionUID = 1L;
318 
319 		InputFieldTuple(long fieldIndex) {
320 			super(Opcode.GET_INPUT_FIELD_CONST, fieldIndex);
321 		}
322 
323 		/**
324 		 * Returns the constant input-field index.
325 		 *
326 		 * @return input-field index
327 		 */
328 		public long getFieldIndex() {
329 			return getValue();
330 		}
331 	}
332 
333 	/**
334 	 * Tuple for an address operand.
335 	 */
336 	public static class AddressTuple extends Tuple {
337 		private static final long serialVersionUID = 1L;
338 		private Address address;
339 
340 		AddressTuple(Opcode opcode, Address address) {
341 			super(opcode);
342 			this.address = address;
343 		}
344 
345 		@Override
346 		public Address getAddress() {
347 			return address;
348 		}
349 
350 		void setAddress(Address address) {
351 			this.address = address;
352 		}
353 
354 		@Override
355 		public String toString() {
356 			return getOpcode().name() + ", " + address;
357 		}
358 	}
359 
360 	/**
361 	 * Tuple for variable offset/global operands.
362 	 */
363 	public static class VariableTuple extends Tuple {
364 		private static final long serialVersionUID = 1L;
365 		private final long variableOffset;
366 		private final boolean global;
367 
368 		VariableTuple(Opcode opcode, long variableOffset, boolean global) {
369 			super(opcode);
370 			this.variableOffset = variableOffset;
371 			this.global = global;
372 		}
373 
374 		/**
375 		 * Returns the variable offset.
376 		 *
377 		 * @return variable offset
378 		 */
379 		public final long getVariableOffset() {
380 			return variableOffset;
381 		}
382 
383 		/**
384 		 * Indicates whether the variable offset belongs to the global frame.
385 		 *
386 		 * @return {@code true} for a global variable
387 		 */
388 		public final boolean isGlobal() {
389 			return global;
390 		}
391 
392 		@Override
393 		public String toString() {
394 			return getOpcode().name() + ", " + variableOffset + ", " + global;
395 		}
396 	}
397 
398 	/**
399 	 * Tuple for scalar compound assignments.
400 	 */
401 	public static final class CompoundAssignTuple extends VariableTuple {
402 		private static final long serialVersionUID = 1L;
403 
404 		CompoundAssignTuple(Opcode opcode, long variableOffset, boolean global) {
405 			super(opcode, variableOffset, global);
406 		}
407 	}
408 
409 	/**
410 	 * Tuple for array compound assignments.
411 	 */
412 	public static final class CompoundAssignArrayTuple extends VariableTuple {
413 		private static final long serialVersionUID = 1L;
414 
415 		CompoundAssignArrayTuple(Opcode opcode, long variableOffset, boolean global) {
416 			super(opcode, variableOffset, global);
417 		}
418 	}
419 
420 	/**
421 	 * Tuple for stack-provided map element compound assignments.
422 	 */
423 	public static final class CompoundAssignMapElementTuple extends NoOperandTuple {
424 		private static final long serialVersionUID = 1L;
425 
426 		CompoundAssignMapElementTuple(Opcode opcode) {
427 			super(opcode);
428 		}
429 	}
430 
431 	/**
432 	 * Tuple for input-field compound assignments.
433 	 */
434 	public static final class CompoundAssignInputFieldTuple extends NoOperandTuple {
435 		private static final long serialVersionUID = 1L;
436 
437 		CompoundAssignInputFieldTuple(Opcode opcode) {
438 			super(opcode);
439 		}
440 	}
441 
442 	/**
443 	 * Tuple for variable dereference.
444 	 */
445 	public static final class DereferenceTuple extends Tuple {
446 		private static final long serialVersionUID = 1L;
447 		private final long variableOffset;
448 		private final boolean array;
449 		private final boolean global;
450 
451 		DereferenceTuple(long variableOffset, boolean array, boolean global) {
452 			super(Opcode.DEREFERENCE);
453 			this.variableOffset = variableOffset;
454 			this.array = array;
455 			this.global = global;
456 		}
457 
458 		/**
459 		 * Returns the variable offset.
460 		 *
461 		 * @return variable offset
462 		 */
463 		public long getVariableOffset() {
464 			return variableOffset;
465 		}
466 
467 		/**
468 		 * Indicates whether this dereference should initialize an array.
469 		 *
470 		 * @return {@code true} when the variable is an array
471 		 */
472 		public boolean isArray() {
473 			return array;
474 		}
475 
476 		/**
477 		 * Indicates whether the variable offset belongs to the global frame.
478 		 *
479 		 * @return {@code true} for a global variable
480 		 */
481 		public boolean isGlobal() {
482 			return global;
483 		}
484 
485 		@Override
486 		public String toString() {
487 			return getOpcode().name() + ", " + variableOffset + ", " + array + ", " + global;
488 		}
489 	}
490 
491 	/**
492 	 * Tuple for boolean operands.
493 	 */
494 	public static final class BooleanTuple extends Tuple {
495 		private static final long serialVersionUID = 1L;
496 		private final boolean value;
497 
498 		BooleanTuple(Opcode opcode, boolean value) {
499 			super(opcode);
500 			this.value = value;
501 		}
502 
503 		/**
504 		 * Returns the boolean operand.
505 		 *
506 		 * @return boolean operand
507 		 */
508 		public boolean getValue() {
509 			return value;
510 		}
511 
512 		@Override
513 		public String toString() {
514 			return getOpcode().name() + ", " + value;
515 		}
516 	}
517 
518 	/**
519 	 * Tuple for sub/gsub against variable-backed values.
520 	 */
521 	public static final class SubstitutionVariableTuple extends VariableTuple {
522 		private static final long serialVersionUID = 1L;
523 		private final boolean globalSubstitution;
524 
525 		SubstitutionVariableTuple(Opcode opcode, long variableOffset, boolean global, boolean globalSubstitution) {
526 			super(opcode, variableOffset, global);
527 			this.globalSubstitution = globalSubstitution;
528 		}
529 
530 		/**
531 		 * Indicates whether this substitution is global.
532 		 *
533 		 * @return {@code true} for {@code gsub}, {@code false} for {@code sub}
534 		 */
535 		public boolean isGlobalSubstitution() {
536 			return globalSubstitution;
537 		}
538 
539 		@Override
540 		public String toString() {
541 			return getOpcode().name() + ", " + getVariableOffset() + ", " + isGlobal() + ", " + globalSubstitution;
542 		}
543 	}
544 
545 	/**
546 	 * Tuple for a precompiled literal regular expression.
547 	 */
548 	public static final class RegexTuple extends Tuple {
549 		private static final long serialVersionUID = 1L;
550 		private final String regex;
551 		private final Pattern pattern;
552 
553 		RegexTuple(String regex, Pattern pattern) {
554 			super(Opcode.REGEXP);
555 			this.regex = regex;
556 			this.pattern = pattern;
557 		}
558 
559 		/**
560 		 * Returns the original regular expression text.
561 		 *
562 		 * @return regular expression text
563 		 */
564 		public String getRegex() {
565 			return regex;
566 		}
567 
568 		/**
569 		 * Returns the precompiled regular expression.
570 		 *
571 		 * @return compiled pattern
572 		 */
573 		public Pattern getPattern() {
574 			return pattern;
575 		}
576 
577 		@Override
578 		public String toString() {
579 			return getOpcode().name() + stringArgument(regex) + patternArgument(pattern);
580 		}
581 	}
582 
583 	/**
584 	 * Tuple for a class check.
585 	 */
586 	public static final class ClassTuple extends Tuple {
587 		private static final long serialVersionUID = 1L;
588 		private final Class<?> type;
589 
590 		ClassTuple(Class<?> type) {
591 			super(Opcode.CHECK_CLASS);
592 			this.type = type;
593 		}
594 
595 		/**
596 		 * Returns the required runtime type.
597 		 *
598 		 * @return required class
599 		 */
600 		public Class<?> getType() {
601 			return type;
602 		}
603 
604 		@Override
605 		public String toString() {
606 			return getOpcode().name() + ", " + type;
607 		}
608 	}
609 
610 	/**
611 	 * Tuple for function definitions.
612 	 */
613 	public static final class FunctionTuple extends Tuple {
614 		private static final long serialVersionUID = 1L;
615 		private final String functionName;
616 		private final long numFormalParams;
617 
618 		FunctionTuple(String functionName, long numFormalParams) {
619 			super(Opcode.FUNCTION);
620 			this.functionName = functionName;
621 			this.numFormalParams = numFormalParams;
622 		}
623 
624 		/**
625 		 * Returns the function name.
626 		 *
627 		 * @return function name
628 		 */
629 		public String getFunctionName() {
630 			return functionName;
631 		}
632 
633 		/**
634 		 * Returns the number of formal parameters.
635 		 *
636 		 * @return formal parameter count
637 		 */
638 		public long getNumFormalParams() {
639 			return numFormalParams;
640 		}
641 
642 		@Override
643 		public String toString() {
644 			return getOpcode().name() + stringArgument(functionName) + ", " + numFormalParams;
645 		}
646 	}
647 
648 	/**
649 	 * Tuple for function calls.
650 	 */
651 	public static final class CallFunctionTuple extends AddressTuple {
652 		private static final long serialVersionUID = 1L;
653 		private transient Supplier<Address> addressSupplier;
654 		private final String functionName;
655 		private final long numFormalParams;
656 		private final long numActualParams;
657 
658 		CallFunctionTuple(
659 				Supplier<Address> addressSupplier,
660 				String functionName,
661 				long numFormalParams,
662 				long numActualParams) {
663 			super(Opcode.CALL_FUNCTION, null);
664 			this.addressSupplier = addressSupplier;
665 			this.functionName = functionName;
666 			this.numFormalParams = numFormalParams;
667 			this.numActualParams = numActualParams;
668 		}
669 
670 		@Override
671 		public Address getAddress() {
672 			Address address = super.getAddress();
673 			if (address == null && addressSupplier != null) {
674 				address = addressSupplier.get();
675 				setAddress(address);
676 				addressSupplier = null;
677 			}
678 			return address;
679 		}
680 
681 		@Override
682 		public void touch(List<Tuple> queue) {
683 			getAddress();
684 			super.touch(queue);
685 		}
686 
687 		/**
688 		 * Returns the function name.
689 		 *
690 		 * @return function name
691 		 */
692 		public String getFunctionName() {
693 			return functionName;
694 		}
695 
696 		/**
697 		 * Returns the number of formal parameters.
698 		 *
699 		 * @return formal parameter count
700 		 */
701 		public long getNumFormalParams() {
702 			return numFormalParams;
703 		}
704 
705 		/**
706 		 * Returns the number of actual parameters at this call site.
707 		 *
708 		 * @return actual parameter count
709 		 */
710 		public long getNumActualParams() {
711 			return numActualParams;
712 		}
713 
714 		@Override
715 		public String toString() {
716 			return getOpcode().name()
717 					+ ", "
718 					+ getAddress()
719 					+ stringArgument(functionName)
720 					+ ", "
721 					+ numFormalParams
722 					+ ", "
723 					+ numActualParams;
724 		}
725 	}
726 
727 	/**
728 	 * Tuple for extension function invocations.
729 	 */
730 	public static final class ExtensionTuple extends Tuple {
731 		private static final long serialVersionUID = 1L;
732 		private final ExtensionFunction function;
733 		private final long argCount;
734 		private final boolean initial;
735 
736 		ExtensionTuple(ExtensionFunction function, long argCount, boolean initial) {
737 			super(Opcode.EXTENSION);
738 			this.function = function;
739 			this.argCount = argCount;
740 			this.initial = initial;
741 		}
742 
743 		/**
744 		 * Returns the extension function metadata.
745 		 *
746 		 * @return extension function
747 		 */
748 		public ExtensionFunction getFunction() {
749 			return function;
750 		}
751 
752 		/**
753 		 * Returns the number of extension arguments.
754 		 *
755 		 * @return argument count
756 		 */
757 		public long getArgCount() {
758 			return argCount;
759 		}
760 
761 		/**
762 		 * Indicates whether this tuple starts an extension call sequence.
763 		 *
764 		 * @return {@code true} for the initial extension call tuple
765 		 */
766 		public boolean isInitial() {
767 			return initial;
768 		}
769 
770 		@Override
771 		public String toString() {
772 			return getOpcode().name() + ", " + function.getKeyword() + ", " + argCount + ", " + initial;
773 		}
774 	}
775 }