1 package io.jawk.backend;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import java.io.Closeable;
26 import java.io.IOException;
27 import java.io.PrintStream;
28 import java.util.AbstractSet;
29 import java.util.Iterator;
30 import java.util.Arrays;
31 import java.util.HashSet;
32 import java.util.LinkedHashMap;
33 import java.util.LinkedHashSet;
34 import java.util.Objects;
35 import java.util.ArrayDeque;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.Deque;
39 import java.util.Enumeration;
40 import java.util.HashMap;
41 import java.util.IdentityHashMap;
42 import java.util.List;
43 import java.util.Locale;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.function.BiConsumer;
47 import java.util.regex.Pattern;
48 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
49 import io.jawk.AwkExpression;
50 import io.jawk.AwkProgram;
51 import io.jawk.AwkSandboxException;
52 import io.jawk.ExitException;
53 import io.jawk.ext.AbstractExtension;
54 import io.jawk.ext.ExtensionFunction;
55 import io.jawk.ext.ForInKeyOrder;
56 import io.jawk.ext.JawkExtension;
57 import io.jawk.intermediate.Address;
58 import io.jawk.intermediate.BuiltinFunction;
59 import io.jawk.intermediate.Opcode;
60 import io.jawk.intermediate.PositionTracker;
61 import io.jawk.intermediate.Tuple;
62 import io.jawk.intermediate.Tuple.BooleanTuple;
63 import io.jawk.intermediate.Tuple.CallFunctionTuple;
64 import io.jawk.intermediate.Tuple.ClassTuple;
65 import io.jawk.intermediate.Tuple.CountAndAppendTuple;
66 import io.jawk.intermediate.Tuple.CountTuple;
67 import io.jawk.intermediate.Tuple.DereferenceTuple;
68 import io.jawk.intermediate.Tuple.ExtensionTuple;
69 import io.jawk.intermediate.Tuple.IndirectCallTuple;
70 import io.jawk.intermediate.Tuple.IndirectFunctionTarget;
71 import io.jawk.intermediate.Tuple.InputFieldTuple;
72 import io.jawk.intermediate.Tuple.LongTuple;
73 import io.jawk.intermediate.Tuple.PushDoubleTuple;
74 import io.jawk.intermediate.Tuple.PushLongTuple;
75 import io.jawk.intermediate.Tuple.PushStringTuple;
76 import io.jawk.intermediate.Tuple.RegexTuple;
77 import io.jawk.intermediate.Tuple.ScalarPopTuple;
78 import io.jawk.intermediate.Tuple.SubstitutionVariableTuple;
79 import io.jawk.intermediate.Tuple.VariableTuple;
80 import io.jawk.intermediate.UninitializedObject;
81 import io.jawk.intermediate.UntypedObject;
82 import io.jawk.jrt.AssocArray;
83 import io.jawk.jrt.AwkRuntimeException;
84 import io.jawk.jrt.AwkSink;
85 import io.jawk.jrt.ConditionPair;
86 import io.jawk.jrt.InputSource;
87 import io.jawk.jrt.JRT;
88 import io.jawk.jrt.VariableManager;
89 import io.jawk.util.AwkSettings;
90 import io.jawk.jrt.BSDRandom;
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 public class AVM implements VariableManager, Closeable {
125
126 private RuntimeStack runtimeStack = new RuntimeStack();
127
128
129
130
131 private Object[] operandStack = new Object[INITIAL_OPERAND_STACK_CAPACITY];
132 private int operandStackSize;
133
134
135
136
137 private final Deque<IndirectArrayArgumentReference> elementArgumentReferences = new ArrayDeque<IndirectArrayArgumentReference>();
138 private List<String> arguments;
139 private boolean sortedArrayKeys;
140 private final Map<String, Object> baseInitialVariables;
141 private final Map<String, Object> baseSpecialVariables;
142 private Map<String, Object> executionInitialVariables;
143 private Map<String, Object> executionSpecialVariables;
144 private JRT jrt;
145 private Map<String, JawkExtension> extensionInstances;
146
147 private static final int INITIAL_OPERAND_STACK_CAPACITY = 64;
148
149
150 private Object pop() {
151 Object value = operandStack[--operandStackSize];
152 operandStack[operandStackSize] = null;
153 return value;
154 }
155
156 private void push(Object o) {
157 if (operandStackSize == operandStack.length) {
158 operandStack = Arrays.copyOf(operandStack, operandStack.length * 2);
159 }
160 operandStack[operandStackSize++] = o;
161 }
162
163 private void clearOperandStack() {
164 Arrays.fill(operandStack, 0, operandStackSize, null);
165 operandStackSize = 0;
166 }
167
168 private final AwkSettings settings;
169 private final boolean profiling;
170 private final Map<Opcode, ProfilingReport.Accumulator> tupleProfilingStats;
171 private final Map<String, ProfilingReport.Accumulator> functionProfilingStats;
172 private final Deque<ActiveFunction> activeProfilingFunctions;
173 private InputSource resolvedInputSource;
174 private AwkExpression installedEvalExpression;
175 private boolean mergedGlobalLayoutActive;
176
177
178 private ForInKeyOrder forInKeyOrder;
179
180
181 private String sourceDescription;
182
183
184 private int currentLineNumber;
185
186
187 private boolean beforeStartHooksExecuted;
188
189
190 private long symtabOffset = NULL_OFFSET;
191
192
193
194
195
196
197
198 public AVM() {
199 this(null, Collections.<String, JawkExtension>emptyMap());
200 }
201
202
203
204
205
206
207
208
209
210 public AVM(final AwkSettings parameters,
211 final Map<String, JawkExtension> extensionInstances) {
212 this(parameters, extensionInstances, false);
213 }
214
215
216
217
218
219
220
221
222
223 public AVM(
224 final AwkSettings parameters,
225 final Map<String, JawkExtension> extensionInstances,
226 final boolean profilingEnabled) {
227 this.settings = parameters != null ? parameters : AwkSettings.DEFAULT_SETTINGS;
228 this.extensionInstances = extensionInstances == null ?
229 Collections.<String, JawkExtension>emptyMap() : extensionInstances;
230 this.profiling = profilingEnabled;
231 if (profilingEnabled) {
232 this.tupleProfilingStats = new java.util.EnumMap<Opcode, ProfilingReport.Accumulator>(Opcode.class);
233 this.functionProfilingStats = new LinkedHashMap<String, ProfilingReport.Accumulator>();
234 this.activeProfilingFunctions = new ArrayDeque<ActiveFunction>();
235 } else {
236 this.tupleProfilingStats = null;
237 this.functionProfilingStats = null;
238 this.activeProfilingFunctions = null;
239 }
240
241 arguments = Collections.emptyList();
242 sortedArrayKeys = this.settings.isUseSortedArrayKeys();
243 baseInitialVariables = new HashMap<String, Object>(this.settings.getVariables());
244 baseSpecialVariables = JRT.copySpecialVariables(baseInitialVariables);
245 executionInitialVariables = baseInitialVariables;
246 executionSpecialVariables = baseSpecialVariables;
247
248 jrt = createJrt();
249 initExtensions();
250 }
251
252
253
254
255
256
257
258
259
260
261
262
263
264 protected JRT createJrt() {
265 return new JRT(this, this.settings.getLocale(), AwkSink.NOP_SINK, null);
266 }
267
268
269
270
271
272
273 protected AwkSettings getSettings() {
274 return settings;
275 }
276
277
278
279
280
281
282 @SuppressFBWarnings("EI_EXPOSE_REP")
283 public JRT getJrt() {
284 return jrt;
285 }
286
287
288
289
290
291
292
293 public void setAwkSink(AwkSink sink) {
294 jrt.setAwkSink(Objects.requireNonNull(sink, "sink"));
295 }
296
297
298
299
300
301
302
303 public void setErrorStream(PrintStream errorStream) {
304 jrt.setErrorStream(errorStream);
305 }
306
307
308
309
310
311
312
313 public void setWarningStream(PrintStream warningStream) {
314 jrt.setWarningStream(warningStream);
315 }
316
317
318
319
320
321
322
323
324
325
326
327
328
329 public void setForInKeyOrder(ForInKeyOrder keyOrder) {
330 forInKeyOrder = keyOrder;
331 }
332
333
334
335
336
337
338 public AwkSink getAwkSink() {
339 return jrt.getAwkSink();
340 }
341
342
343
344
345
346
347 protected Locale getLocale() {
348 return jrt.getLocale();
349 }
350
351
352
353
354
355
356
357
358
359 public Object eval(AwkExpression expression) throws IOException {
360 AwkExpression compiledExpression = Objects.requireNonNull(expression, "expression");
361 installExpressionMetadata(compiledExpression);
362
363 try {
364 executeTuples(compiledExpression.top());
365 } catch (ExitException e) {
366
367
368 throwExitException = false;
369 exitCode = 0;
370 throw new IllegalStateException("eval(AwkExpression) cannot execute EXIT opcodes.", e);
371 }
372 return operandStackSize == 0 ? null : JRT.toJavaScalar(pop());
373 }
374
375
376
377
378
379
380
381
382
383 public Object eval(AwkExpression expression, InputSource inputSource) throws IOException {
384 return eval(expression, inputSource, null);
385 }
386
387
388
389
390
391
392
393
394
395
396
397
398 public Object eval(
399 AwkExpression expression,
400 InputSource inputSource,
401 Map<String, Object> variableOverrides)
402 throws IOException {
403 prepareForEval(inputSource, Collections.<String>emptyList(), variableOverrides);
404 return eval(expression);
405 }
406
407
408
409
410
411
412
413
414
415 public void execute(AwkProgram program, InputSource inputSource) throws ExitException, IOException {
416 execute(program, inputSource, Collections.<String>emptyList(), null);
417 }
418
419
420
421
422
423
424
425
426
427
428 public void execute(AwkProgram program, InputSource inputSource, List<String> runtimeArguments)
429 throws ExitException,
430 IOException {
431 execute(program, inputSource, runtimeArguments, null);
432 }
433
434
435
436
437
438
439
440
441
442
443
444
445
446 public void execute(
447 AwkProgram program,
448 InputSource inputSource,
449 List<String> runtimeArguments,
450 Map<String, Object> variableOverrides)
451 throws ExitException,
452 IOException {
453 AwkProgram compiledProgram = Objects.requireNonNull(program, "program");
454 InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
455 resetRuntimeState(runtimeArguments, variableOverrides);
456 installProgramMetadata(compiledProgram);
457
458 jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
459 if (!executionSpecialVariables.isEmpty()) {
460 jrt.applySpecialVariables(executionSpecialVariables);
461 }
462 rebindResolvedInputSource(resolvedSource);
463 executeTuples(compiledProgram.top());
464 }
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479 public void executePersistingGlobals(AwkProgram program, InputSource inputSource)
480 throws ExitException,
481 IOException {
482 executePersistingGlobals(program, inputSource, Collections.<String>emptyList(), null);
483 }
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499 public void executePersistingGlobals(
500 AwkProgram program,
501 InputSource inputSource,
502 List<String> runtimeArguments)
503 throws ExitException,
504 IOException {
505 executePersistingGlobals(program, inputSource, runtimeArguments, null);
506 }
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524 public void executePersistingGlobals(
525 AwkProgram program,
526 InputSource inputSource,
527 List<String> runtimeArguments,
528 Map<String, Object> variableOverrides)
529 throws ExitException,
530 IOException {
531 AwkProgram compiledProgram = Objects.requireNonNull(program, "program");
532 InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
533 mergeRuntimeState(runtimeArguments, variableOverrides, compiledProgram);
534
535 jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
536 if (!executionSpecialVariables.isEmpty()) {
537 jrt.applySpecialVariables(executionSpecialVariables);
538 }
539 rebindResolvedInputSource(resolvedSource);
540 executeTuples(compiledProgram.top());
541 }
542
543
544
545
546
547
548
549 public void clearPersistentGlobals() {
550 runtimeStack.clearGlobals();
551 mergedGlobalLayoutActive = false;
552 }
553
554
555
556
557
558
559
560
561
562
563 public Map<String, Object> snapshotPersistentMemory() {
564 return new LinkedHashMap<>(collectPersistentGlobalValues());
565 }
566
567
568
569
570
571
572
573
574
575
576
577 public void restorePersistentMemory(Map<String, Object> snapshot) {
578 Map<String, Object> restoredSnapshot = Objects.requireNonNull(snapshot, "snapshot");
579 Map<String, Object> restoredGlobals = filterToPersistentEligible(restoredSnapshot);
580 runtimeStack.clearGlobals();
581 if (!restoredGlobals.isEmpty()) {
582 runtimeStack.rebindGlobals(new ArrayList<>(restoredGlobals.keySet()));
583 applyGlobalsToStack(restoredGlobals);
584 }
585 mergedGlobalLayoutActive = false;
586 }
587
588 private void initExtensions() {
589 if (extensionInstances.isEmpty()) {
590 return;
591 }
592 Set<JawkExtension> initialized = new LinkedHashSet<JawkExtension>();
593 for (JawkExtension extension : extensionInstances.values()) {
594 if (initialized.add(extension)) {
595 extension.init(this, jrt, settings);
596 }
597 }
598 }
599
600
601
602 private long environOffset = NULL_OFFSET;
603 private long argcOffset = NULL_OFFSET;
604 private long argvOffset = NULL_OFFSET;
605
606 private static final Integer ZERO = Integer.valueOf(0);
607 private static final Integer ONE = Integer.valueOf(1);
608
609
610 private final BSDRandom randomNumberGenerator = new BSDRandom(1);
611
612
613
614
615
616 private Address exitAddress = null;
617
618
619
620
621
622 private Address endFileAddress = null;
623
624
625
626
627
628
629
630 private Address nextFileAddress = null;
631
632
633
634
635
636
637 private Address nextAddress = null;
638
639
640
641
642
643 private boolean withinBeginFileBlocks = false;
644
645
646
647
648
649 private boolean withinEndFileBlocks = false;
650
651
652
653
654
655 private boolean inputFileLoopStarted = false;
656
657
658
659
660
661
662 private boolean mainInputLoopStarted = false;
663
664
665
666
667
668 private boolean withinEndBlocks = false;
669
670
671
672
673 private int exitCode = 0;
674
675
676
677
678 private boolean throwExitException = false;
679
680
681
682
683
684
685 private Map<String, Integer> globalVariableOffsets;
686
687
688
689
690 private Map<String, Boolean> globalVariableArrays;
691 private Set<String> functionNames = Collections.emptySet();
692 private Map<String, Integer> initializedEvalGlobalVariableOffsets;
693 private Map<String, Boolean> initializedEvalGlobalVariableArrays;
694
695
696
697
698
699
700
701
702
703
704 public boolean prepareForEval(String input) throws IOException {
705 return prepareForEval(new SingleRecordInputSource(input), Collections.<String>emptyList(), null);
706 }
707
708
709
710
711
712
713
714
715
716
717
718 public boolean prepareForEval(InputSource inputSource) throws IOException {
719 return prepareForEval(inputSource, Collections.<String>emptyList(), null);
720 }
721
722 private boolean prepareForEval(
723 InputSource inputSource,
724 List<String> runtimeArguments,
725 Map<String, Object> variableOverrides)
726 throws IOException {
727 InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
728 resetRuntimeState(runtimeArguments, variableOverrides);
729 rebindResolvedInputSource(resolvedSource);
730
731 jrt.jrtCloseAll();
732 jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
733 if (!executionSpecialVariables.isEmpty()) {
734 jrt.applySpecialVariables(executionSpecialVariables);
735 }
736 return jrt.consumeInputForEval(resolvedInputSource);
737 }
738
739 private void resetRuntimeState(List<String> runtimeArguments, Map<String, Object> variableOverrides) {
740 resetTransientRuntimeState(runtimeArguments, variableOverrides);
741 runtimeStack.clearGlobals();
742 }
743
744 private void resetTransientRuntimeState(List<String> runtimeArguments, Map<String, Object> variableOverrides) {
745
746 clearOperandStack();
747 elementArgumentReferences.clear();
748 environOffset = NULL_OFFSET;
749 argcOffset = NULL_OFFSET;
750 argvOffset = NULL_OFFSET;
751 symtabOffset = NULL_OFFSET;
752 exitAddress = null;
753 endFileAddress = null;
754 nextFileAddress = null;
755 nextAddress = null;
756 withinBeginFileBlocks = false;
757 withinEndFileBlocks = false;
758 inputFileLoopStarted = false;
759 mainInputLoopStarted = false;
760 withinEndBlocks = false;
761 exitCode = 0;
762 throwExitException = false;
763 globalVariableOffsets = null;
764 globalVariableArrays = null;
765 functionNames = Collections.emptySet();
766 initializedEvalGlobalVariableOffsets = null;
767 initializedEvalGlobalVariableArrays = null;
768 installedEvalExpression = null;
769 mergedGlobalLayoutActive = false;
770 runtimeStack.resetTransientState();
771 randomNumberGenerator.setSeed(1);
772
773 prepareExecutionInputs(runtimeArguments, variableOverrides);
774 }
775
776 private void installExpressionMetadata(AwkExpression compiledExpression) {
777 if (installedEvalExpression == compiledExpression) {
778 return;
779 }
780 globalVariableOffsets = compiledExpression.getGlobalVariableOffsetMap();
781 globalVariableArrays = compiledExpression.getGlobalVariableAarrayMap();
782 functionNames = compiledExpression.getFunctionNameSet();
783 installedEvalExpression = compiledExpression;
784 }
785
786 private void installProgramMetadata(AwkProgram compiledProgram) {
787 globalVariableOffsets = compiledProgram.getGlobalVariableOffsetMap();
788 globalVariableArrays = compiledProgram.getGlobalVariableAarrayMap();
789 functionNames = compiledProgram.getFunctionNameSet();
790 sourceDescription = compiledProgram.getSourceDescription();
791 exitAddress = compiledProgram.getExitAddress();
792 endFileAddress = compiledProgram.getEndFileAddress();
793 nextFileAddress = compiledProgram.getNextFileAddress();
794 nextAddress = compiledProgram.getNextAddress();
795 }
796
797 private void rebindResolvedInputSource(InputSource resolvedSource) {
798 InputSource previousResolvedSource = resolvedInputSource;
799 if (previousResolvedSource != null && previousResolvedSource != resolvedSource) {
800 closeInputSource(previousResolvedSource);
801 }
802 resolvedInputSource = resolvedSource;
803
804
805 jrt.bindStandardInput(resolvedSource);
806 }
807
808 private boolean hasCompatibleEvalGlobalLayout(long numGlobals) {
809 Object[] globals = runtimeStack.getNumGlobals();
810 return globals != null
811 && globals.length == numGlobals
812 && Objects.equals(initializedEvalGlobalVariableOffsets, globalVariableOffsets)
813 && Objects.equals(initializedEvalGlobalVariableArrays, globalVariableArrays);
814 }
815
816
817
818
819
820
821
822
823
824
825 private void mergeRuntimeState(
826 List<String> runtimeArguments,
827 Map<String, Object> variableOverrides,
828 AwkProgram compiledProgram) {
829 Map<String, Object> carriedGlobals = collectPersistentGlobalValues();
830 resetTransientRuntimeState(runtimeArguments, variableOverrides);
831 installProgramMetadata(compiledProgram);
832
833 Map<String, Object> basePersistentSeeds = collectBasePersistentGlobalSeeds();
834 Map<String, Object> executionUserSeeds = collectExecutionUserGlobalSeeds(variableOverrides);
835 List<String> mergedGlobalNamesByOffset = buildMergedGlobalNamesByOffset(
836 carriedGlobals,
837 basePersistentSeeds,
838 executionUserSeeds);
839
840 runtimeStack.rebindGlobals(mergedGlobalNamesByOffset);
841 applyGlobalsToStack(carriedGlobals);
842 applyGlobalsToStack(basePersistentSeeds);
843 applyGlobalsToStack(executionUserSeeds);
844 mergedGlobalLayoutActive = true;
845 }
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860 private boolean hasCompatiblePersistentGlobalLayout(long numGlobals) {
861 Object[] globals = runtimeStack.getNumGlobals();
862 if (!mergedGlobalLayoutActive
863 || globals == null
864 || globalVariableOffsets == null
865 || globals.length < numGlobals) {
866 return false;
867 }
868 for (Map.Entry<String, Integer> entry : globalVariableOffsets.entrySet()) {
869 int offset = entry.getValue().intValue();
870 if (offset < 0 || offset >= globals.length || !entry.getKey().equals(runtimeStack.getGlobalName(offset))) {
871 return false;
872 }
873 }
874 return true;
875 }
876
877
878
879
880
881
882
883
884
885
886
887
888 private void applyExecutionInitialVariablesToGlobalSlots(boolean skipPersistentEligibleGlobals) {
889 for (Map.Entry<String, Object> entry : executionInitialVariables.entrySet()) {
890 String key = entry.getKey();
891 if (skipPersistentEligibleGlobals && isPersistentEligibleGlobal(key)) {
892 continue;
893 }
894 if (functionNames.contains(key)) {
895 throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + key + ").");
896 }
897 Integer offsetObj = globalVariableOffsets.get(key);
898 Boolean arrayObj = globalVariableArrays.get(key);
899 if (offsetObj != null) {
900 Object obj = normalizeExternalVariableValue(entry.getValue());
901 if (arrayObj.booleanValue()) {
902 if (obj instanceof Map) {
903 runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
904 } else {
905 throw new IllegalArgumentException(
906 "Cannot assign a scalar to a non-scalar variable (" + key + ").");
907 }
908 } else {
909 runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
910 }
911 }
912 }
913 }
914
915
916
917
918
919
920
921
922
923
924 private void prepareExecutionInputs(
925 List<String> runtimeArguments,
926 Map<String, Object> variableOverrides) {
927 this.arguments = runtimeArguments != null ? new ArrayList<>(runtimeArguments) : Collections.<String>emptyList();
928
929 if (variableOverrides == null || variableOverrides.isEmpty()) {
930 executionInitialVariables = baseInitialVariables;
931 executionSpecialVariables = baseSpecialVariables;
932 } else {
933 executionInitialVariables = new HashMap<>(baseInitialVariables);
934 executionInitialVariables.putAll(variableOverrides);
935
936 Map<String, Object> specialOverrides = JRT.copySpecialVariables(variableOverrides);
937 if (specialOverrides.isEmpty()) {
938 executionSpecialVariables = baseSpecialVariables;
939 } else {
940 executionSpecialVariables = new HashMap<>(baseSpecialVariables);
941 executionSpecialVariables.putAll(specialOverrides);
942 }
943 }
944 }
945
946
947
948
949
950
951
952
953 private Map<String, Object> filterToPersistentEligible(Map<String, Object> source) {
954 Map<String, Object> result = new LinkedHashMap<>();
955 for (Map.Entry<String, Object> entry : source.entrySet()) {
956 if (isPersistentEligibleGlobal(entry.getKey())) {
957 result.put(entry.getKey(), entry.getValue());
958 }
959 }
960 return result;
961 }
962
963
964
965
966
967
968 private Map<String, Object> collectPersistentGlobalValues() {
969 return filterToPersistentEligible(runtimeStack.snapshotGlobalVariables());
970 }
971
972
973
974
975
976
977
978 private Map<String, Object> collectBasePersistentGlobalSeeds() {
979 Map<String, Object> basePersistentSeeds = new LinkedHashMap<>();
980 for (Map.Entry<String, Object> entry : baseInitialVariables.entrySet()) {
981 String name = entry.getKey();
982 if (isPersistentEligibleGlobal(name)) {
983 validateSeededGlobalName(name);
984 Object value = normalizeExternalVariableValue(entry.getValue());
985 validateSeededGlobalValue(name, value);
986 basePersistentSeeds.put(name, value);
987 }
988 }
989 return basePersistentSeeds;
990 }
991
992
993
994
995
996
997
998
999
1000
1001
1002 private Map<String, Object> collectExecutionUserGlobalSeeds(Map<String, Object> variableOverrides) {
1003 Map<String, Object> executionUserSeeds = new LinkedHashMap<>();
1004 if (variableOverrides != null) {
1005 for (Map.Entry<String, Object> entry : variableOverrides.entrySet()) {
1006 String name = entry.getKey();
1007 if (isPersistentEligibleGlobal(name)) {
1008 validateSeededGlobalName(name);
1009 Object value = normalizeExternalVariableValue(entry.getValue());
1010 validateSeededGlobalValue(name, value);
1011 executionUserSeeds.put(name, value);
1012 }
1013 }
1014 }
1015 return executionUserSeeds;
1016 }
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031 private List<String> buildMergedGlobalNamesByOffset(
1032 Map<String, Object> carriedGlobals,
1033 Map<String, Object> basePersistentSeeds,
1034 Map<String, Object> executionUserSeeds) {
1035 LinkedHashSet<String> orderedNames = new LinkedHashSet<>();
1036 List<Map.Entry<String, Integer>> compiledGlobals = new ArrayList<>(globalVariableOffsets.entrySet());
1037 compiledGlobals.sort(java.util.Comparator.comparingInt(Map.Entry::getValue));
1038 for (Map.Entry<String, Integer> entry : compiledGlobals) {
1039 orderedNames.add(entry.getKey());
1040 }
1041 orderedNames.addAll(carriedGlobals.keySet());
1042 orderedNames.addAll(basePersistentSeeds.keySet());
1043 orderedNames.addAll(executionUserSeeds.keySet());
1044 return new ArrayList<>(orderedNames);
1045 }
1046
1047
1048
1049
1050
1051
1052
1053 private void applyGlobalsToStack(Map<String, Object> globals) {
1054 for (Map.Entry<String, Object> entry : globals.entrySet()) {
1055 runtimeStack.setGlobalVariable(entry.getKey(), entry.getValue());
1056 }
1057 }
1058
1059
1060
1061
1062
1063
1064
1065
1066 private boolean isPersistentEligibleGlobal(String name) {
1067 return name != null
1068 && !isManagedSpecialVariable(name)
1069 && !NON_PERSISTENT_GLOBALS.contains(name);
1070 }
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083 private boolean isManagedSpecialVariable(String name) {
1084 if (!JRT.isJrtManagedSpecialVariable(name)) {
1085 return false;
1086 }
1087 if (JRT.isGawkOnlySpecialVariable(name)) {
1088 return !settings.isPosix();
1089 }
1090 return true;
1091 }
1092
1093
1094
1095
1096
1097
1098
1099
1100 private void validateSeededGlobalName(String name) {
1101 if (functionNames.contains(name)) {
1102 throw new IllegalArgumentException("Cannot assign a value to a function name (" + name + ").");
1103 }
1104 }
1105
1106
1107
1108
1109
1110
1111
1112
1113 private void validateSeededGlobalValue(String name, Object value) {
1114 Boolean arrayObj = globalVariableArrays.get(name);
1115 if (Boolean.TRUE.equals(arrayObj) && !(value instanceof Map)) {
1116 throw new IllegalArgumentException("Cannot assign a scalar to a non-scalar variable (" + name + ").");
1117 }
1118 }
1119
1120
1121
1122
1123
1124
1125
1126
1127 private void executeTuples(PositionTracker position)
1128 throws ExitException,
1129 IOException {
1130 Map<Long, ConditionPair> conditionPairs = null;
1131 Opcode opcode = null;
1132 long tupleStartNanos = 0L;
1133 try {
1134 while (!position.isEOF()) {
1135
1136 Tuple tuple = position.current();
1137 opcode = tuple.getOpcode();
1138 if (profiling) {
1139 tupleStartNanos = beforeProfiledTuple(tuple, opcode);
1140 }
1141
1142 switch (opcode) {
1143 case PRINT: {
1144 execPrint((CountTuple) tuple);
1145 position.next();
1146 break;
1147 }
1148 case PRINT_TO_FILE: {
1149 execPrintToFile((CountAndAppendTuple) tuple);
1150 position.next();
1151 break;
1152 }
1153 case PRINT_TO_PIPE: {
1154 execPrintToPipe((CountTuple) tuple);
1155 position.next();
1156 break;
1157 }
1158 case PRINTF: {
1159 execPrintf((CountTuple) tuple);
1160 position.next();
1161 break;
1162 }
1163 case PRINTF_TO_FILE: {
1164 execPrintfToFile((CountAndAppendTuple) tuple);
1165 position.next();
1166 break;
1167 }
1168 case PRINTF_TO_PIPE: {
1169 execPrintfToPipe((CountTuple) tuple);
1170 position.next();
1171 break;
1172 }
1173 case SPRINTF: {
1174
1175
1176
1177
1178 CountTuple countTuple = (CountTuple) tuple;
1179 long numArgs = countTuple.getCount();
1180 push(sprintfFunction(numArgs));
1181 position.next();
1182 break;
1183 }
1184 case LENGTH: {
1185 execLength((CountTuple) tuple);
1186 position.next();
1187 break;
1188 }
1189 case PUSH_LONG: {
1190
1191 PushLongTuple pushTuple = (PushLongTuple) tuple;
1192 push(pushTuple.getValue());
1193 position.next();
1194 break;
1195 }
1196 case PUSH_DOUBLE: {
1197
1198 PushDoubleTuple pushTuple = (PushDoubleTuple) tuple;
1199 push(pushTuple.getValue());
1200 position.next();
1201 break;
1202 }
1203 case PUSH_STRING: {
1204
1205 PushStringTuple pushTuple = (PushStringTuple) tuple;
1206 push(pushTuple.getValue());
1207 position.next();
1208 break;
1209 }
1210 case POP: {
1211
1212 Object discarded = pop();
1213 if (tuple instanceof ScalarPopTuple) {
1214 checkScalar(discarded);
1215 }
1216 position.next();
1217 break;
1218 }
1219 case IFFALSE: {
1220
1221
1222
1223
1224
1225
1226 boolean jump = !jrt.toBoolean(pop());
1227 if (jump) {
1228 position.jump(tuple.getAddress());
1229 } else {
1230 position.next();
1231 }
1232 break;
1233 }
1234 case TO_NUMBER: {
1235
1236
1237
1238
1239
1240 boolean val = jrt.toBoolean(pop());
1241 push(val ? ONE : ZERO);
1242 position.next();
1243 break;
1244 }
1245 case IFTRUE: {
1246
1247
1248
1249
1250
1251
1252 boolean jump = jrt.toBoolean(pop());
1253 if (jump) {
1254 position.jump(tuple.getAddress());
1255 } else {
1256 position.next();
1257 }
1258 break;
1259 }
1260 case NOT: {
1261
1262
1263 Object o = pop();
1264
1265 boolean result = jrt.toBoolean(o);
1266
1267 if (result) {
1268 push(0);
1269 } else {
1270 push(1);
1271 }
1272 position.next();
1273 break;
1274 }
1275 case NEGATE: {
1276
1277
1278 push(JRT.negate(pop()));
1279 position.next();
1280 break;
1281 }
1282 case UNARY_PLUS: {
1283
1284
1285
1286 push(numericValueOf(pop()));
1287 position.next();
1288 break;
1289 }
1290 case GOTO: {
1291
1292
1293 position.jump(tuple.getAddress());
1294 break;
1295 }
1296 case NOP: {
1297
1298 position.next();
1299 break;
1300 }
1301 case CONCAT: {
1302
1303
1304 String s2 = jrt.toAwkString(pop());
1305 String s1 = jrt.toAwkString(pop());
1306 String resultString = s1 + s2;
1307 push(resultString);
1308 position.next();
1309 break;
1310 }
1311 case MULTI_CONCAT: {
1312
1313
1314 CountTuple countTuple = (CountTuple) tuple;
1315 int count = (int) countTuple.getCount();
1316
1317
1318
1319
1320 String[] values = new String[count];
1321 int resultLength = 0;
1322 for (int i = count - 1; i >= 0; i--) {
1323 values[i] = jrt.toAwkString(pop());
1324 resultLength += values[i].length();
1325 }
1326 StringBuilder resultString = new StringBuilder(resultLength);
1327 for (String value : values) {
1328 resultString.append(value);
1329 }
1330 push(resultString.toString());
1331 position.next();
1332 break;
1333 }
1334 case ASSIGN:
1335 case ASSIGN_NOPUSH: {
1336
1337
1338
1339 VariableTuple variableTuple = (VariableTuple) tuple;
1340 Object value = pop();
1341 assign(
1342 variableTuple.getVariableOffset(),
1343 value,
1344 variableTuple.isGlobal(),
1345 position,
1346 opcode == Opcode.ASSIGN);
1347 position.next();
1348 break;
1349 }
1350 case ASSIGN_ARRAY: {
1351
1352
1353
1354
1355 Object arrIdx = pop();
1356 Object rhs = pop();
1357 if (rhs == null) {
1358 rhs = BLANK;
1359 }
1360 VariableTuple variableTuple = (VariableTuple) tuple;
1361 long offset = variableTuple.getVariableOffset();
1362 boolean isGlobal = variableTuple.isGlobal();
1363 assignArray(offset, arrIdx, rhs, isGlobal);
1364 position.next();
1365 break;
1366 }
1367 case ASSIGN_MAP_ELEMENT: {
1368
1369
1370
1371 Object arrIdx = pop();
1372 Map<Object, Object> array = toMap(pop());
1373 Object rhs = pop();
1374 if (rhs == null) {
1375 rhs = BLANK;
1376 }
1377 assignMapElement(array, arrIdx, rhs);
1378 position.next();
1379 break;
1380 }
1381 case PLUS_EQ_ARRAY:
1382 case MINUS_EQ_ARRAY:
1383 case MULT_EQ_ARRAY:
1384 case DIV_EQ_ARRAY:
1385 case MOD_EQ_ARRAY:
1386 case POW_EQ_ARRAY: {
1387 execCompoundAssignArray(opcode, (VariableTuple) tuple);
1388 position.next();
1389 break;
1390 }
1391 case PLUS_EQ_MAP_ELEMENT:
1392 case MINUS_EQ_MAP_ELEMENT:
1393 case MULT_EQ_MAP_ELEMENT:
1394 case DIV_EQ_MAP_ELEMENT:
1395 case MOD_EQ_MAP_ELEMENT:
1396 case POW_EQ_MAP_ELEMENT: {
1397 execCompoundAssignMapElement(opcode);
1398 position.next();
1399 break;
1400 }
1401
1402 case ASSIGN_AS_INPUT: {
1403
1404 jrt.setInputLine(pop());
1405 push(jrt.getInputLine());
1406 position.next();
1407 break;
1408 }
1409
1410 case ASSIGN_AS_INPUT_FIELD: {
1411
1412
1413 Object fieldNumObj = pop();
1414 long fieldNum = JRT.parseFieldNumber(fieldNumObj);
1415 Object value = pop();
1416 push(value);
1417 if (fieldNum == 0) {
1418 jrt.setInputLine(value);
1419 jrt.jrtParseFields();
1420 } else {
1421 jrt.jrtSetInputField(value, fieldNum);
1422 }
1423 position.next();
1424 break;
1425 }
1426 case PLUS_EQ:
1427 case MINUS_EQ:
1428 case MULT_EQ:
1429 case DIV_EQ:
1430 case MOD_EQ:
1431 case POW_EQ: {
1432 execCompoundAssignVariable(opcode, (VariableTuple) tuple);
1433 position.next();
1434 break;
1435 }
1436 case PLUS_EQ_INPUT_FIELD:
1437 case MINUS_EQ_INPUT_FIELD:
1438 case MULT_EQ_INPUT_FIELD:
1439 case DIV_EQ_INPUT_FIELD:
1440 case MOD_EQ_INPUT_FIELD:
1441 case POW_EQ_INPUT_FIELD: {
1442 execCompoundAssignInputField(opcode);
1443 position.next();
1444 break;
1445 }
1446 case INC: {
1447
1448
1449 VariableTuple variableTuple = (VariableTuple) tuple;
1450 inc(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1451 position.next();
1452 break;
1453 }
1454 case DEC: {
1455
1456
1457 VariableTuple variableTuple = (VariableTuple) tuple;
1458 dec(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1459 position.next();
1460 break;
1461 }
1462 case POSTINC: {
1463
1464
1465 pop();
1466 VariableTuple variableTuple = (VariableTuple) tuple;
1467 push(inc(variableTuple.getVariableOffset(), variableTuple.isGlobal()));
1468 position.next();
1469 break;
1470 }
1471 case POSTDEC: {
1472
1473
1474 pop();
1475 VariableTuple variableTuple = (VariableTuple) tuple;
1476 push(dec(variableTuple.getVariableOffset(), variableTuple.isGlobal()));
1477 position.next();
1478 break;
1479 }
1480 case INC_ARRAY_REF: {
1481
1482
1483
1484 VariableTuple variableTuple = (VariableTuple) tuple;
1485 boolean isGlobal = variableTuple.isGlobal();
1486 Map<Object, Object> aa = ensureMapVariable(variableTuple.getVariableOffset(), isGlobal);
1487 Object key = pop();
1488 checkScalar(key);
1489 Object o = aa.get(key);
1490 aa.put(key, JRT.inc(blankToZero(o)));
1491 position.next();
1492 break;
1493 }
1494 case DEC_ARRAY_REF: {
1495
1496
1497
1498 VariableTuple variableTuple = (VariableTuple) tuple;
1499 boolean isGlobal = variableTuple.isGlobal();
1500 Map<Object, Object> aa = ensureMapVariable(variableTuple.getVariableOffset(), isGlobal);
1501 Object key = pop();
1502 checkScalar(key);
1503 Object o = aa.get(key);
1504 aa.put(key, JRT.dec(blankToZero(o)));
1505 position.next();
1506 break;
1507 }
1508 case INC_MAP_REF: {
1509
1510
1511 Object key = pop();
1512 checkScalar(key);
1513 Map<Object, Object> aa = toMap(pop());
1514 Object o = aa.get(key);
1515 aa.put(key, JRT.inc(blankToZero(o)));
1516 position.next();
1517 break;
1518 }
1519 case DEC_MAP_REF: {
1520
1521
1522 Object key = pop();
1523 checkScalar(key);
1524 Map<Object, Object> aa = toMap(pop());
1525 Object o = aa.get(key);
1526 aa.put(key, JRT.dec(blankToZero(o)));
1527 position.next();
1528 break;
1529 }
1530 case INC_DOLLAR_REF: {
1531
1532 long fieldnum = JRT.parseFieldNumber(pop());
1533
1534 Object numObj = blankToZero(jrt.jrtGetInputField(fieldnum));
1535 Object original = numericValueOf(numObj);
1536 setNumOnJRT(fieldnum, JRT.inc(original));
1537
1538 push(original);
1539
1540 position.next();
1541 break;
1542 }
1543 case DEC_DOLLAR_REF: {
1544
1545
1546 long fieldnum = JRT.parseFieldNumber(pop());
1547
1548 Object numObj = blankToZero(jrt.jrtGetInputField(fieldnum));
1549 Object original = numericValueOf(numObj);
1550 setNumOnJRT(fieldnum, JRT.dec(original));
1551
1552 push(original);
1553
1554 position.next();
1555 break;
1556 }
1557 case DEREFERENCE: {
1558
1559
1560 DereferenceTuple dereferenceTuple = (DereferenceTuple) tuple;
1561 boolean isGlobal = dereferenceTuple.isGlobal();
1562 long offset = dereferenceTuple.getVariableOffset();
1563 push(resolveVariable(offset, isGlobal, dereferenceTuple.isArray()));
1564 position.next();
1565 break;
1566 }
1567 case PEEK_DEREFERENCE: {
1568 VariableTuple variableTuple = (VariableTuple) tuple;
1569 Object value = runtimeStack
1570 .getVariable(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1571 push(
1572 value instanceof ArgumentReference ?
1573 resolveRawArgumentReference((ArgumentReference) value) : value);
1574 position.next();
1575 break;
1576 }
1577 case PUSH_INDIRECT_ARGUMENT: {
1578 VariableTuple variableTuple = (VariableTuple) tuple;
1579 Object scalarValue = runtimeStack
1580 .getVariable(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1581
1582
1583 if (scalarValue instanceof ArgumentReference) {
1584 push(resolveUserFunctionArgument(scalarValue));
1585 } else if (isUntyped(scalarValue)) {
1586 push(
1587 new IndirectArgumentReference(
1588 runtimeStack.getVariableFrame(variableTuple.isGlobal()),
1589 variableTuple.getVariableOffset(),
1590 scalarValue));
1591 } else {
1592 push(scalarValue);
1593 }
1594 position.next();
1595 break;
1596 }
1597 case PUSH_INDIRECT_ARRAY_ARGUMENT: {
1598 Object idx = pop();
1599 checkScalar(idx);
1600 Map<Object, Object> map = toMap(pop());
1601 Object scalarValue = JRT.getAssocArrayValue(map, idx);
1602
1603
1604 push(
1605 isUntyped(scalarValue) ?
1606 new IndirectArrayArgumentReference(map, idx, scalarValue) : scalarValue);
1607 position.next();
1608 break;
1609 }
1610 case DEREF_ARRAY: {
1611
1612 Object idx = pop();
1613 checkScalar(idx);
1614 Map<Object, Object> map = toMap(pop());
1615 Object o = JRT.getAssocArrayValue(map, idx);
1616 push(o);
1617 position.next();
1618 break;
1619 }
1620 case ENSURE_ARRAY_ELEMENT: {
1621
1622
1623 Object idx = pop();
1624 Map<Object, Object> map = toMap(pop());
1625 push(ensureArrayInArray(map, idx));
1626 position.next();
1627 break;
1628 }
1629 case PEEK_ARRAY_ELEMENT: {
1630
1631 Object idx = pop();
1632 checkScalar(idx);
1633 Map<Object, Object> map = toMap(pop());
1634 if (map instanceof AssocArray && !JRT.containsAwkKey(map, idx)) {
1635 push(AssocArray.UNTYPED);
1636 } else {
1637 Object value = map.get(idx);
1638 push(value != null ? value : AssocArray.UNTYPED);
1639 }
1640 position.next();
1641 break;
1642 }
1643 case SRAND: {
1644
1645
1646 CountTuple countTuple = (CountTuple) tuple;
1647 long numArgs = countTuple.getCount();
1648 int seed;
1649 if (numArgs == 0) {
1650
1651 seed = JRT.timeSeed();
1652 } else {
1653 seed = (int) JRT.toDouble(pop());
1654 }
1655 int previousSeed = randomNumberGenerator.getSeed();
1656 randomNumberGenerator.setSeed(seed);
1657 push(previousSeed);
1658 position.next();
1659 break;
1660 }
1661 case RAND: {
1662 push(randomNumberGenerator.nextDouble());
1663 position.next();
1664 break;
1665 }
1666 case INTFUNC: {
1667
1668 push(JRT.truncateToScalar(JRT.toDouble(pop())));
1669 position.next();
1670 break;
1671 }
1672 case SQRT: {
1673
1674 push(Math.sqrt(JRT.toDouble(pop())));
1675 position.next();
1676 break;
1677 }
1678 case LOG: {
1679
1680 push(Math.log(JRT.toDouble(pop())));
1681 position.next();
1682 break;
1683 }
1684 case EXP: {
1685
1686 push(Math.exp(JRT.toDouble(pop())));
1687 position.next();
1688 break;
1689 }
1690 case SIN: {
1691
1692 push(Math.sin(JRT.toDouble(pop())));
1693 position.next();
1694 break;
1695 }
1696 case COS: {
1697
1698 push(Math.cos(JRT.toDouble(pop())));
1699 position.next();
1700 break;
1701 }
1702 case ATAN2: {
1703
1704
1705 double d2 = JRT.toDouble(pop());
1706 double d1 = JRT.toDouble(pop());
1707 push(Math.atan2(d1, d2));
1708 position.next();
1709 break;
1710 }
1711 case MATCH: {
1712 execMatch();
1713 position.next();
1714 break;
1715 }
1716 case INDEX: {
1717
1718
1719 String s2 = jrt.toAwkString(pop());
1720 String s1 = jrt.toAwkString(pop());
1721 push(jrt.index(s1, s2));
1722 position.next();
1723 break;
1724 }
1725 case SUB_FOR_DOLLAR_0: {
1726 execSubForDollar0((BooleanTuple) tuple);
1727 position.next();
1728 break;
1729 }
1730 case SUB_FOR_DOLLAR_REFERENCE: {
1731 execSubForDollarReference((BooleanTuple) tuple);
1732 position.next();
1733 break;
1734 }
1735 case SUB_FOR_VARIABLE: {
1736 execSubForVariable((SubstitutionVariableTuple) tuple, position);
1737 position.next();
1738 break;
1739 }
1740 case SUB_FOR_ARRAY_REFERENCE: {
1741 execSubForArrayReference((SubstitutionVariableTuple) tuple);
1742 position.next();
1743 break;
1744 }
1745 case SUB_FOR_MAP_REFERENCE: {
1746 execSubForMapReference((BooleanTuple) tuple);
1747 position.next();
1748 break;
1749 }
1750 case SPLIT: {
1751 execSplit((CountTuple) tuple, position);
1752 position.next();
1753 break;
1754 }
1755 case SUBSTR: {
1756 execSubstr((CountTuple) tuple);
1757 position.next();
1758 break;
1759 }
1760 case TOLOWER: {
1761
1762 push(jrt.toAwkString(pop()).toLowerCase());
1763 position.next();
1764 break;
1765 }
1766 case TOUPPER: {
1767
1768 push(jrt.toAwkString(pop()).toUpperCase());
1769 position.next();
1770 break;
1771 }
1772 case SYSTEM: {
1773
1774 String s = jrt.toAwkString(pop());
1775 push(jrt.jrtSystem(s));
1776 position.next();
1777 break;
1778 }
1779 case SWAP: {
1780
1781
1782 Object o1 = pop();
1783 Object o2 = pop();
1784 push(o1);
1785 push(o2);
1786 position.next();
1787 break;
1788 }
1789 case CMP_EQ: {
1790
1791
1792 Object o2 = pop();
1793 Object o1 = pop();
1794 push(jrt.compare(o1, o2, 0) ? ONE : ZERO);
1795 position.next();
1796 break;
1797 }
1798 case CMP_LT: {
1799
1800
1801 Object o2 = pop();
1802 Object o1 = pop();
1803 push(jrt.compare(o1, o2, -1) ? ONE : ZERO);
1804 position.next();
1805 break;
1806 }
1807 case CMP_GT: {
1808
1809
1810 Object o2 = pop();
1811 Object o1 = pop();
1812 push(jrt.compare(o1, o2, 1) ? ONE : ZERO);
1813 position.next();
1814 break;
1815 }
1816 case MATCHES: {
1817
1818
1819 Object o2 = pop();
1820 Object o1 = pop();
1821
1822
1823 push(jrt.matches(jrt.toAwkString(o1), o2) ? 1 : 0);
1824 position.next();
1825 break;
1826 }
1827 case ADD: {
1828
1829
1830 Object o2 = pop();
1831 Object o1 = pop();
1832 push(JRT.add(o1, o2));
1833 position.next();
1834 break;
1835 }
1836 case SUBTRACT: {
1837
1838
1839 Object o2 = pop();
1840 Object o1 = pop();
1841 push(JRT.subtract(o1, o2));
1842 position.next();
1843 break;
1844 }
1845 case MULTIPLY: {
1846
1847
1848 Object o2 = pop();
1849 Object o1 = pop();
1850 push(JRT.multiply(o1, o2));
1851 position.next();
1852 break;
1853 }
1854 case DIVIDE: {
1855
1856
1857 Object o2 = pop();
1858 Object o1 = pop();
1859 push(JRT.divide(o1, o2));
1860 position.next();
1861 break;
1862 }
1863 case MOD: {
1864
1865
1866 Object o2 = pop();
1867 Object o1 = pop();
1868 push(JRT.mod(o1, o2));
1869 position.next();
1870 break;
1871 }
1872 case POW: {
1873
1874
1875 Object o2 = pop();
1876 Object o1 = pop();
1877 push(JRT.pow(o1, o2));
1878 position.next();
1879 break;
1880 }
1881 case DUP: {
1882
1883 Object o = pop();
1884 push(o);
1885 push(o);
1886 position.next();
1887 break;
1888 }
1889 case KEYLIST: {
1890 Object o = pop();
1891 if (isUntyped(o)) {
1892 push(new ArrayDeque<>());
1893 position.next();
1894 break;
1895 }
1896 if (!(o instanceof Map)) {
1897 throw new AwkRuntimeException("Attempting to use a scalar as an array.");
1898 }
1899 @SuppressWarnings("unchecked")
1900 Map<Object, Object> map = (Map<Object, Object>) o;
1901 push(new ArrayDeque<>(forInKeyOrder == null ? map.keySet() : forInKeyOrder.order(map)));
1902 position.next();
1903 break;
1904 }
1905 case IS_EMPTY_KEYLIST: {
1906
1907
1908 Object o = pop();
1909 if (o == null || !(o instanceof Deque)) {
1910 throw new AwkRuntimeException(
1911 position.lineNumber(),
1912 "Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
1913 }
1914 Deque<?> keylist = (Deque<?>) o;
1915 if (keylist.isEmpty()) {
1916 position.jump(tuple.getAddress());
1917 } else {
1918 position.next();
1919 }
1920 break;
1921 }
1922 case GET_FIRST_AND_REMOVE_FROM_KEYLIST: {
1923
1924 Object o = pop();
1925 if (o == null || !(o instanceof Deque)) {
1926 throw new AwkRuntimeException(
1927 position.lineNumber(),
1928 "Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
1929 }
1930
1931 Deque<?> keylist = (Deque<?>) o;
1932 push(keylist.removeFirst());
1933 position.next();
1934 break;
1935 }
1936 case CHECK_CLASS: {
1937
1938
1939 ClassTuple checkTuple = (ClassTuple) tuple;
1940 Object o = pop();
1941 if (!checkTuple.getType().isInstance(o)) {
1942 throw new AwkRuntimeException(
1943 position.lineNumber(),
1944 "Verification failed. Top-of-stack = " + o.getClass() + " isn't an instance of "
1945 + checkTuple.getType());
1946 }
1947 push(o);
1948 position.next();
1949 break;
1950 }
1951 case CONSUME_INPUT: {
1952
1953
1954 mainInputLoopStarted = true;
1955 if (jrt.consumeInput(resolvedInputSource)) {
1956 position.next();
1957 } else {
1958 position.jump(tuple.getAddress());
1959 }
1960 break;
1961 }
1962 case CONSUME_FILE_INPUT: {
1963
1964
1965 mainInputLoopStarted = true;
1966 withinBeginFileBlocks = false;
1967 if (jrt.consumeCurrentFileInput(resolvedInputSource)) {
1968 position.next();
1969 } else {
1970 withinEndFileBlocks = true;
1971 position.jump(tuple.getAddress());
1972 }
1973 break;
1974 }
1975 case NEXT_FILE: {
1976
1977 inputFileLoopStarted = true;
1978 withinEndFileBlocks = false;
1979 if (jrt.advanceToNextFile(resolvedInputSource)) {
1980 withinBeginFileBlocks = true;
1981 position.next();
1982 } else {
1983 position.jump(tuple.getAddress());
1984 }
1985 break;
1986 }
1987 case EXEC_NEXTFILE: {
1988 executeNextfile(position);
1989 break;
1990 }
1991 case EXEC_NEXT: {
1992 executeNext(position);
1993 break;
1994 }
1995
1996 case GETLINE_INPUT: {
1997 checkGetlineAllowed(position);
1998 boolean consumed = isMainInputFileBounded() ?
1999 jrt.consumeCurrentFileInput(resolvedInputSource) : jrt.consumeInput(resolvedInputSource);
2000 push(consumed ? 1 : 0);
2001 position.next();
2002 break;
2003 }
2004 case GETLINE_INPUT_TO_TARGET: {
2005
2006 checkGetlineAllowed(position);
2007 Object input = isMainInputFileBounded() ?
2008 jrt.consumeCurrentFileInputToTarget(resolvedInputSource) : jrt.consumeInputToTarget(resolvedInputSource);
2009 if (input != null) {
2010 push(1);
2011 push(input);
2012 position.next();
2013 } else {
2014 push(0);
2015 position.jump(tuple.getAddress());
2016 }
2017 break;
2018 }
2019 case USE_AS_FILE_INPUT: {
2020
2021
2022 String s = jrt.toAwkString(pop());
2023 Integer retcode = jrt.jrtConsumeFileInputForGetline(s);
2024 push(retcode);
2025 if (retcode.intValue() == 1) {
2026 push(jrt.toInputScalar(jrt.jrtGetInputString()));
2027 position.next();
2028 } else {
2029 position.jump(tuple.getAddress());
2030 }
2031 break;
2032 }
2033 case USE_AS_COMMAND_INPUT: {
2034
2035
2036 String s = jrt.toAwkString(pop());
2037 Integer retcode = jrt.jrtConsumeCommandInputForGetline(s);
2038 push(retcode);
2039 if (retcode.intValue() == 1) {
2040 push(jrt.toInputScalar(jrt.jrtGetInputString()));
2041 position.next();
2042 } else {
2043 position.jump(tuple.getAddress());
2044 }
2045 break;
2046 }
2047 case ENVIRON_OFFSET: {
2048
2049
2050 populateEnviron(((LongTuple) tuple).getValue());
2051 position.next();
2052 break;
2053 }
2054 case ARGC_OFFSET: {
2055
2056 populateArgc(((LongTuple) tuple).getValue());
2057 position.next();
2058 break;
2059 }
2060 case ARGV_OFFSET: {
2061
2062 populateArgv(((LongTuple) tuple).getValue());
2063 position.next();
2064 break;
2065 }
2066 case GET_INPUT_FIELD: {
2067
2068 Object fieldNumber = pop();
2069 push(jrt.jrtGetInputField(fieldNumber));
2070 position.next();
2071 break;
2072 }
2073 case GET_INPUT_FIELD_CONST: {
2074 InputFieldTuple inputFieldTuple = (InputFieldTuple) tuple;
2075 long fieldnum = inputFieldTuple.getFieldIndex();
2076 push(jrt.jrtGetInputField(fieldnum));
2077 position.next();
2078 break;
2079 }
2080 case APPLY_RS: {
2081 jrt.applyRS(jrt.getRSVar());
2082 position.next();
2083 break;
2084 }
2085 case CALL_FUNCTION: {
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095 CallFunctionTuple callTuple = (CallFunctionTuple) tuple;
2096 Address funcAddr = callTuple.getAddress();
2097 long numFormalParams = callTuple.getNumFormalParams();
2098 long numActualParams = callTuple.getNumActualParams();
2099 runtimeStack.pushFrame(numFormalParams, position.currentIndex());
2100
2101 for (long i = numActualParams - 1; i >= 0; i--) {
2102 Object argument = pop();
2103 adoptElementArgumentReference(argument);
2104 runtimeStack.setVariable(i, argument, false);
2105 }
2106 position.jump(funcAddr);
2107
2108 break;
2109 }
2110 case INDIRECT_CALL: {
2111 execIndirectCall((IndirectCallTuple) tuple, position, tupleStartNanos);
2112 break;
2113 }
2114 case FUNCTION: {
2115
2116
2117
2118
2119 position.next();
2120 break;
2121 }
2122 case WARNING: {
2123 jrt.printWarning(((Tuple.WarningTuple) tuple).getMessage());
2124 position.next();
2125 break;
2126 }
2127 case SET_RETURN_RESULT: {
2128
2129 runtimeStack.setReturnValue(pop());
2130 position.next();
2131 break;
2132 }
2133 case RETURN_FROM_FUNCTION: {
2134 releaseElementArgumentReferences();
2135 position.jump(runtimeStack.popFrame());
2136 push(runtimeStack.getReturnValue());
2137 position.next();
2138 break;
2139 }
2140 case SET_NUM_GLOBALS: {
2141 execSetNumGlobals((CountTuple) tuple);
2142 position.next();
2143 break;
2144 }
2145 case BEFORE_START_HOOKS: {
2146 runBeforeStartHooks();
2147 position.next();
2148 break;
2149 }
2150 case UPDATE_SYMTAB: {
2151 execUpdateSymtab(((LongTuple) tuple).getValue());
2152 position.next();
2153 break;
2154 }
2155 case UPDATE_FUNCTAB: {
2156 execUpdateFunctab(((LongTuple) tuple).getValue());
2157 position.next();
2158 break;
2159 }
2160 case CLOSE: {
2161
2162 String s = jrt.toAwkString(pop());
2163 push(jrt.jrtClose(s));
2164 position.next();
2165 break;
2166 }
2167 case APPLY_SUBSEP: {
2168 execApplySubsep((CountTuple) tuple);
2169 position.next();
2170 break;
2171 }
2172 case APPLY_SUBSEP_UNDER_TOP: {
2173
2174
2175 Object top = pop();
2176 execApplySubsep((CountTuple) tuple);
2177 push(top);
2178 position.next();
2179 break;
2180 }
2181 case DELETE_ARRAY_ELEMENT: {
2182
2183
2184
2185 VariableTuple variableTuple = (VariableTuple) tuple;
2186 long offset = variableTuple.getVariableOffset();
2187 boolean isGlobal = variableTuple.isGlobal();
2188 Map<Object, Object> aa = getMapVariable(offset, isGlobal);
2189 Object key = pop();
2190 checkScalar(key);
2191 if (aa != null) {
2192 aa.remove(key);
2193 detachMissingArrayArgumentReferences(aa);
2194 }
2195 position.next();
2196 break;
2197 }
2198 case DELETE_MAP_ELEMENT: {
2199
2200
2201 Object key = pop();
2202 checkScalar(key);
2203 Map<Object, Object> aa = toMap(pop());
2204 aa.remove(key);
2205 detachMissingArrayArgumentReferences(aa);
2206 position.next();
2207 break;
2208 }
2209 case DELETE_ARRAY: {
2210
2211
2212
2213 VariableTuple variableTuple = (VariableTuple) tuple;
2214 long offset = variableTuple.getVariableOffset();
2215 boolean isGlobal = variableTuple.isGlobal();
2216 Map<Object, Object> array = getMapVariable(offset, isGlobal);
2217 if (array != null) {
2218 array.clear();
2219 detachMissingArrayArgumentReferences(array);
2220 }
2221 position.next();
2222 break;
2223 }
2224 case SET_WITHIN_END_BLOCKS: {
2225
2226 BooleanTuple endBlocksTuple = (BooleanTuple) tuple;
2227 withinEndBlocks = endBlocksTuple.getValue();
2228 position.next();
2229 break;
2230 }
2231 case EXIT_WITHOUT_CODE:
2232 case EXIT_WITH_CODE: {
2233 if (opcode == Opcode.EXIT_WITH_CODE) {
2234
2235 exitCode = (int) JRT.toDouble(pop());
2236 }
2237 throwExitException = true;
2238 withinBeginFileBlocks = false;
2239 withinEndFileBlocks = false;
2240
2241
2242 if (!withinEndBlocks && exitAddress != null) {
2243 resetCallState();
2244 position.jump(exitAddress);
2245 } else {
2246
2247
2248 clearOperandStack();
2249 throw new ExitException(exitCode, "The AWK script requested an exit");
2250
2251 }
2252 break;
2253 }
2254 case REGEXP: {
2255
2256 RegexTuple regexTuple = (RegexTuple) tuple;
2257 Pattern pattern = regexTuple.getPattern();
2258 push(pattern);
2259 position.next();
2260 break;
2261 }
2262 case CONDITION_PAIR: {
2263
2264
2265
2266 if (conditionPairs == null) {
2267 conditionPairs = new HashMap<Long, ConditionPair>();
2268 }
2269 long currentIndex = position.currentIndex();
2270 ConditionPair cp = conditionPairs.get(currentIndex);
2271 if (cp == null) {
2272 cp = new ConditionPair();
2273 conditionPairs.put(currentIndex, cp);
2274 }
2275 boolean end = jrt.toBoolean(pop());
2276 boolean start = jrt.toBoolean(pop());
2277 push(cp.update(start, end) ? ONE : ZERO);
2278 position.next();
2279 break;
2280 }
2281 case CONDITION_PAIR_IN_RANGE: {
2282
2283 if (conditionPairs == null) {
2284 conditionPairs = new HashMap<Long, ConditionPair>();
2285 }
2286 long id = ((LongTuple) tuple).getValue();
2287 ConditionPair cp = conditionPairs.get(id);
2288 if (cp == null) {
2289 cp = new ConditionPair();
2290 conditionPairs.put(id, cp);
2291 }
2292 push(cp.isWithin() ? ONE : ZERO);
2293 position.next();
2294 break;
2295 }
2296 case CONDITION_PAIR_ENTER: {
2297
2298
2299
2300 conditionPairs.get(((LongTuple) tuple).getValue()).enter();
2301 position.next();
2302 break;
2303 }
2304 case CONDITION_PAIR_LEAVE: {
2305
2306 conditionPairs.get(((LongTuple) tuple).getValue()).leave();
2307 position.next();
2308 break;
2309 }
2310 case IS_IN: {
2311
2312 Object arr = pop();
2313 Object arg = pop();
2314 checkScalar(arg);
2315 if (isUntyped(arr)) {
2316 push(ZERO);
2317 position.next();
2318 break;
2319 }
2320 if (!(arr instanceof Map)) {
2321 throw new AwkRuntimeException("Attempting to use a scalar as an array.");
2322 }
2323 @SuppressWarnings("unchecked")
2324 Map<Object, Object> aa = (Map<Object, Object>) arr;
2325 boolean result = JRT.containsAwkKey(aa, arg);
2326 push(result ? ONE : ZERO);
2327 position.next();
2328 break;
2329 }
2330 case THIS: {
2331
2332
2333
2334
2335 position.next();
2336 break;
2337 }
2338 case EXTENSION: {
2339
2340
2341
2342
2343
2344 ExtensionTuple extensionTuple = (ExtensionTuple) tuple;
2345 ExtensionFunction function = extensionTuple.getFunction();
2346 long numArgs = extensionTuple.getArgCount();
2347 push(
2348 invokeExtension(
2349 function,
2350 popArguments(numArgs),
2351 position.lineNumber()));
2352
2353 position.next();
2354 break;
2355 }
2356 case ASSIGN_NF: {
2357 Object v = pop();
2358 jrt.setNF(v);
2359 push(v);
2360 position.next();
2361 break;
2362 }
2363 case PUSH_NF: {
2364 push(jrt.getNF());
2365 position.next();
2366 break;
2367 }
2368 case ASSIGN_NR: {
2369 Object v = pop();
2370 jrt.setNR(v);
2371 push(v);
2372 position.next();
2373 break;
2374 }
2375 case PUSH_NR: {
2376 push(jrt.getNR());
2377 position.next();
2378 break;
2379 }
2380 case ASSIGN_FNR: {
2381 Object v = pop();
2382 jrt.setFNR(v);
2383 push(v);
2384 position.next();
2385 break;
2386 }
2387 case PUSH_FNR: {
2388 push(jrt.getFNR());
2389 position.next();
2390 break;
2391 }
2392 case ASSIGN_FS: {
2393 Object v = pop();
2394 jrt.setFS(v);
2395 push(v);
2396 position.next();
2397 break;
2398 }
2399 case ASSIGN_IGNORECASE: {
2400 Object v = pop();
2401 jrt.setIGNORECASE(v);
2402 push(v);
2403 position.next();
2404 break;
2405 }
2406 case PUSH_IGNORECASE: {
2407 push(jrt.getIGNORECASEVar());
2408 position.next();
2409 break;
2410 }
2411 case PUSH_FS: {
2412 push(jrt.getFSVar());
2413 position.next();
2414 break;
2415 }
2416 case ASSIGN_RS: {
2417 Object v = pop();
2418 jrt.setRS(v);
2419 push(v);
2420 position.next();
2421 break;
2422 }
2423 case PUSH_RS: {
2424 push(jrt.getRSVar());
2425 position.next();
2426 break;
2427 }
2428 case ASSIGN_OFS: {
2429 Object v = pop();
2430 jrt.setOFS(v);
2431 push(v);
2432 position.next();
2433 break;
2434 }
2435 case PUSH_OFS: {
2436 push(jrt.getOFSVar());
2437 position.next();
2438 break;
2439 }
2440 case ASSIGN_ORS: {
2441 Object v = pop();
2442 jrt.setORS(v);
2443 push(v);
2444 position.next();
2445 break;
2446 }
2447 case PUSH_ORS: {
2448 push(jrt.getORSVar());
2449 position.next();
2450 break;
2451 }
2452 case ASSIGN_RSTART: {
2453 Object v = pop();
2454 jrt.setRSTART(v);
2455 push(v);
2456 position.next();
2457 break;
2458 }
2459 case PUSH_RSTART: {
2460 push(jrt.getRSTART());
2461 position.next();
2462 break;
2463 }
2464 case ASSIGN_RLENGTH: {
2465 Object v = pop();
2466 jrt.setRLENGTH(v);
2467 push(v);
2468 position.next();
2469 break;
2470 }
2471 case PUSH_RLENGTH: {
2472 push(jrt.getRLENGTH());
2473 position.next();
2474 break;
2475 }
2476 case ASSIGN_FILENAME: {
2477 Object v = pop();
2478 jrt.setFILENAMEViaJrt(v);
2479 push(v == null ? "" : v);
2480 position.next();
2481 break;
2482 }
2483 case PUSH_FILENAME: {
2484 push(jrt.getFILENAME());
2485 position.next();
2486 break;
2487 }
2488 case ASSIGN_ERRNO: {
2489 Object v = pop();
2490 jrt.setERRNO(v);
2491 push(v == null ? "" : v);
2492 position.next();
2493 break;
2494 }
2495 case PUSH_ERRNO: {
2496 push(jrt.getERRNO());
2497 position.next();
2498 break;
2499 }
2500 case ASSIGN_ARGIND: {
2501 Object v = pop();
2502 jrt.setARGIND(v);
2503 push(v == null ? ZERO : v);
2504 position.next();
2505 break;
2506 }
2507 case PUSH_ARGIND: {
2508 push(jrt.getARGIND());
2509 position.next();
2510 break;
2511 }
2512 case ASSIGN_SUBSEP: {
2513 Object v = pop();
2514 jrt.setSUBSEP(v);
2515 push(v);
2516 position.next();
2517 break;
2518 }
2519 case PUSH_SUBSEP: {
2520 push(jrt.getSUBSEPVar());
2521 position.next();
2522 break;
2523 }
2524 case ASSIGN_CONVFMT: {
2525 Object v = pop();
2526 jrt.setCONVFMT(v);
2527 push(v);
2528 position.next();
2529 break;
2530 }
2531 case PUSH_CONVFMT: {
2532 push(jrt.getCONVFMTVar());
2533 position.next();
2534 break;
2535 }
2536 case ASSIGN_OFMT: {
2537 Object v = pop();
2538 jrt.setOFMT(v);
2539 push(v);
2540 position.next();
2541 break;
2542 }
2543 case PUSH_OFMT: {
2544 push(getOFMT());
2545 position.next();
2546 break;
2547 }
2548 case ASSIGN_ARGC: {
2549 Object v = pop();
2550 if (argcOffset == NULL_OFFSET) {
2551 throw new AwkRuntimeException("ARGC is read-only (not materialized).");
2552 }
2553 runtimeStack.setVariable(argcOffset, v, true);
2554 push(v);
2555 position.next();
2556 break;
2557 }
2558 case PUSH_ARGC: {
2559 if (argcOffset == NULL_OFFSET) {
2560 push(getARGC());
2561 } else {
2562 push(runtimeStack.getVariable(argcOffset, true));
2563 }
2564 position.next();
2565 break;
2566 }
2567 default:
2568 throw new Error("invalid opcode: " + position.opcode());
2569 }
2570 if (profiling) {
2571 afterProfiledTuple(opcode, tupleStartNanos);
2572 }
2573 }
2574
2575 } catch (ExitException ee) {
2576 if (profiling && (opcode == Opcode.EXIT_WITH_CODE || opcode == Opcode.EXIT_WITHOUT_CODE)) {
2577 afterProfiledTuple(opcode, tupleStartNanos);
2578 }
2579 throw ee;
2580 } catch (IOException ioe) {
2581 resetCallState();
2582 throw ioe;
2583 } catch (RuntimeException re) {
2584 resetCallState();
2585 if (re instanceof AwkSandboxException) {
2586 throw re;
2587 }
2588 throw new AwkRuntimeException(position.lineNumber(), re.getMessage(), re);
2589 } catch (AssertionError ae) {
2590 resetCallState();
2591 throw ae;
2592 }
2593
2594
2595 if (throwExitException) {
2596 throw new ExitException(exitCode, "The AWK script requested an exit");
2597 }
2598 }
2599
2600
2601
2602
2603 public void resetProfiling() {
2604 if (!profiling) {
2605 return;
2606 }
2607 tupleProfilingStats.clear();
2608 functionProfilingStats.clear();
2609 activeProfilingFunctions.clear();
2610 }
2611
2612
2613
2614
2615
2616
2617 public ProfilingReport getProfilingReport() {
2618 if (!profiling) {
2619 return ProfilingReport.empty();
2620 }
2621 return new ProfilingReport(tupleProfilingStats, functionProfilingStats);
2622 }
2623
2624
2625
2626
2627
2628 private void execCompoundAssignVariable(Opcode opcode, VariableTuple variableTuple) {
2629
2630
2631
2632 long offset = variableTuple.getVariableOffset();
2633 boolean isGlobal = variableTuple.isGlobal();
2634 Object o1 = blankToZero(resolveVariable(offset, isGlobal, false));
2635 Object o2 = pop();
2636 Object ans;
2637 switch (opcode) {
2638 case PLUS_EQ:
2639 ans = JRT.add(o1, o2);
2640 break;
2641 case MINUS_EQ:
2642 ans = JRT.subtract(o1, o2);
2643 break;
2644 case MULT_EQ:
2645 ans = JRT.multiply(o1, o2);
2646 break;
2647 case DIV_EQ:
2648 ans = JRT.divide(o1, o2);
2649 break;
2650 case MOD_EQ:
2651 ans = JRT.mod(o1, o2);
2652 break;
2653 case POW_EQ:
2654 ans = JRT.pow(o1, o2);
2655 break;
2656 default:
2657 throw new Error("Invalid opcode here: " + opcode);
2658 }
2659 push(ans);
2660 runtimeStack.setVariable(offset, ans, isGlobal);
2661 }
2662
2663 private void execCompoundAssignArray(Opcode opcode, VariableTuple variableTuple) {
2664
2665
2666
2667
2668 Object arrIdx = pop();
2669 Object rhs = pop();
2670 if (rhs == null) {
2671 rhs = BLANK;
2672 }
2673 long offset = variableTuple.getVariableOffset();
2674 boolean isGlobal = variableTuple.isGlobal();
2675
2676 Map<Object, Object> array = ensureMapVariable(offset, isGlobal);
2677 checkScalar(arrIdx);
2678 Object o = blankToZero(array.get(arrIdx));
2679
2680 Object newVal;
2681
2682 switch (opcode) {
2683 case PLUS_EQ_ARRAY:
2684 newVal = JRT.add(o, rhs);
2685 break;
2686 case MINUS_EQ_ARRAY:
2687 newVal = JRT.subtract(o, rhs);
2688 break;
2689 case MULT_EQ_ARRAY:
2690 newVal = JRT.multiply(o, rhs);
2691 break;
2692 case DIV_EQ_ARRAY:
2693 newVal = JRT.divide(o, rhs);
2694 break;
2695 case MOD_EQ_ARRAY:
2696 newVal = JRT.mod(o, rhs);
2697 break;
2698 case POW_EQ_ARRAY:
2699 newVal = JRT.pow(o, rhs);
2700 break;
2701 default:
2702 throw new Error("Invalid op code here: " + opcode);
2703 }
2704
2705 assignArray(offset, arrIdx, newVal, isGlobal);
2706 }
2707
2708 private void execCompoundAssignMapElement(Opcode opcode) {
2709
2710
2711
2712 Object arrIdx = pop();
2713 Map<Object, Object> array = toMap(pop());
2714 Object rhs = pop();
2715 if (rhs == null) {
2716 rhs = BLANK;
2717 }
2718
2719 checkScalar(arrIdx);
2720 Object o = blankToZero(array.get(arrIdx));
2721 Object newVal;
2722
2723 switch (opcode) {
2724 case PLUS_EQ_MAP_ELEMENT:
2725 newVal = JRT.add(o, rhs);
2726 break;
2727 case MINUS_EQ_MAP_ELEMENT:
2728 newVal = JRT.subtract(o, rhs);
2729 break;
2730 case MULT_EQ_MAP_ELEMENT:
2731 newVal = JRT.multiply(o, rhs);
2732 break;
2733 case DIV_EQ_MAP_ELEMENT:
2734 newVal = JRT.divide(o, rhs);
2735 break;
2736 case MOD_EQ_MAP_ELEMENT:
2737 newVal = JRT.mod(o, rhs);
2738 break;
2739 case POW_EQ_MAP_ELEMENT:
2740 newVal = JRT.pow(o, rhs);
2741 break;
2742 default:
2743 throw new Error("Invalid op code here: " + opcode);
2744 }
2745
2746 assignMapElement(array, arrIdx, newVal);
2747 }
2748
2749 private void execCompoundAssignInputField(Opcode opcode) {
2750
2751
2752
2753
2754 long fieldnum = JRT.parseFieldNumber(pop());
2755 Object incval = pop();
2756
2757
2758 Object numObj = blankToZero(jrt.jrtGetInputField(fieldnum));
2759 Object num;
2760 switch (opcode) {
2761 case PLUS_EQ_INPUT_FIELD:
2762 num = JRT.add(numObj, incval);
2763 break;
2764 case MINUS_EQ_INPUT_FIELD:
2765 num = JRT.subtract(numObj, incval);
2766 break;
2767 case MULT_EQ_INPUT_FIELD:
2768 num = JRT.multiply(numObj, incval);
2769 break;
2770 case DIV_EQ_INPUT_FIELD:
2771 num = JRT.divide(numObj, incval);
2772 break;
2773 case MOD_EQ_INPUT_FIELD:
2774 num = JRT.mod(numObj, incval);
2775 break;
2776 case POW_EQ_INPUT_FIELD:
2777 num = JRT.pow(numObj, incval);
2778 break;
2779 default:
2780 throw new Error("Invalid opcode here: " + opcode);
2781 }
2782 setNumOnJRT(fieldnum, num);
2783
2784
2785 push(num);
2786 }
2787
2788 private void execIndirectCall(IndirectCallTuple callTuple, PositionTracker position, long tupleStartNanos) {
2789 Object[] actualArguments = popArguments(callTuple.getNumActualParams());
2790 String requestedName = jrt.toAwkString(pop());
2791 String qualifiedName = normalizeIndirectFunctionName(requestedName);
2792 IndirectFunctionTarget target = callTuple.getUserFunctions().get(qualifiedName);
2793 if (target != null) {
2794 long formalCount = target.getNumFormalParams();
2795 if (actualArguments.length > formalCount) {
2796 jrt
2797 .printWarning(
2798 "gawk: "
2799 + callTuple.getSourceName()
2800 + ":"
2801 + callTuple.getSourceLine()
2802 + ": warning: function `"
2803 + qualifiedName
2804 + "' called with more arguments than declared");
2805 }
2806 if (profiling) {
2807 activeProfilingFunctions.push(new ActiveFunction(qualifiedName, tupleStartNanos));
2808 }
2809 runtimeStack.pushFrame(formalCount, position.currentIndex());
2810 adoptElementArgumentReferences(actualArguments);
2811 int copiedArgumentCount = Math.min(actualArguments.length, (int) formalCount);
2812 for (int i = 0; i < copiedArgumentCount; i++) {
2813 runtimeStack.setVariable(i, actualArguments[i], false);
2814 }
2815 position.jump(target.getAddress());
2816 return;
2817 }
2818
2819 String awkName = requestedName.startsWith("awk::") ?
2820 requestedName.substring("awk::".length()) : requestedName;
2821 BuiltinFunction builtin = BuiltinFunction.of(awkName);
2822 if (builtin != null) {
2823 resolveIndirectArguments(actualArguments, builtin);
2824 push(invokeIndirectBuiltin(builtin, actualArguments, position.lineNumber()));
2825 position.next();
2826 return;
2827 }
2828 ExtensionFunction extensionFunction = callTuple.getExtensionFunctions().get(awkName);
2829 if (extensionFunction != null) {
2830 resolveIndirectArguments(actualArguments, extensionFunction);
2831 if (profiling) {
2832 activeProfilingFunctions.push(new ActiveFunction(awkName, tupleStartNanos));
2833 }
2834 try {
2835 push(
2836 invokeExtension(
2837 extensionFunction,
2838 actualArguments,
2839 position.lineNumber()));
2840 } finally {
2841 if (profiling) {
2842 recordFunctionExit(System.nanoTime());
2843 }
2844 }
2845 position.next();
2846 return;
2847 }
2848 throw new AwkRuntimeException(
2849 position.lineNumber(),
2850 "function `" + qualifiedName + "' is not defined");
2851 }
2852
2853 private void execPrint(CountTuple tuple) throws IOException {
2854 long numArgs = tuple.getCount();
2855 jrt.printDefault(numArgs == 0 ? new Object[] { jrt.jrtGetInputField(0) } : popArguments(numArgs));
2856 }
2857
2858 private void execPrintToFile(CountAndAppendTuple tuple) throws IOException {
2859 String key = jrt.toAwkString(pop());
2860 long numArgs = tuple.getCount();
2861 jrt
2862 .printToFile(
2863 key,
2864 tuple.isAppend(),
2865 numArgs == 0 ? new Object[]
2866 { jrt.jrtGetInputField(0) } : popArguments(numArgs));
2867 }
2868
2869 private void execPrintToPipe(CountTuple tuple) throws IOException {
2870 String cmd = jrt.toAwkString(pop());
2871 long numArgs = tuple.getCount();
2872 jrt.printToProcess(cmd, numArgs == 0 ? new Object[] { jrt.jrtGetInputField(0) } : popArguments(numArgs));
2873 }
2874
2875 private void execPrintf(CountTuple tuple) throws IOException {
2876 long numArgs = tuple.getCount();
2877 Object[] values = popArguments(numArgs - 1);
2878 String format = jrt.toAwkString(pop());
2879 jrt.printfDefault(format, values);
2880 }
2881
2882 private void execPrintfToFile(CountAndAppendTuple tuple) throws IOException {
2883 String key = jrt.toAwkString(pop());
2884 long numArgs = tuple.getCount();
2885 Object[] values = popArguments(numArgs - 1);
2886 String format = jrt.toAwkString(pop());
2887 jrt.printfToFile(key, tuple.isAppend(), format, values);
2888 }
2889
2890 private void execPrintfToPipe(CountTuple tuple) throws IOException {
2891 String cmd = jrt.toAwkString(pop());
2892 long numArgs = tuple.getCount();
2893 Object[] values = popArguments(numArgs - 1);
2894 String format = jrt.toAwkString(pop());
2895 jrt.printfToProcess(cmd, format, values);
2896 }
2897
2898 private void execLength(CountTuple tuple) {
2899 long num = tuple.getCount();
2900 if (num == 0) {
2901 push(jrt.jrtGetInputField(0).toString().length());
2902 return;
2903 }
2904 Object value = pop();
2905 if (value instanceof ArgumentReference) {
2906 value = resolveLengthArgumentReference((ArgumentReference) value);
2907 }
2908 push(lengthOf(value));
2909 }
2910
2911 private Object lengthOf(Object value) {
2912 return value instanceof Map ?
2913 Long.valueOf(((Map<?, ?>) value).size()) : Integer.valueOf(jrt.toAwkString(value).length());
2914 }
2915
2916 private String normalizeIndirectFunctionName(String functionName) {
2917 return functionName.startsWith("awk::") ? functionName.substring("awk::".length()) : functionName;
2918 }
2919
2920
2921
2922
2923
2924
2925
2926
2927 private void resetCallState() {
2928 if (profiling) {
2929 recordAllFunctionExits(System.nanoTime());
2930 }
2931 runtimeStack.popAllFrames();
2932 elementArgumentReferences.clear();
2933 clearOperandStack();
2934 }
2935
2936
2937
2938
2939
2940
2941
2942 private void adoptElementArgumentReferences(Object[] actualArguments) {
2943 for (Object argument : actualArguments) {
2944 adoptElementArgumentReference(argument);
2945 }
2946 }
2947
2948 private void adoptElementArgumentReference(Object argument) {
2949 if (argument instanceof IndirectArrayArgumentReference) {
2950 IndirectArrayArgumentReference reference = (IndirectArrayArgumentReference) argument;
2951 if (reference.ownerFrame < 0) {
2952 reference.ownerFrame = runtimeStack.frameCount();
2953 elementArgumentReferences.push(reference);
2954 }
2955 }
2956 }
2957
2958
2959
2960
2961
2962
2963 private void releaseElementArgumentReferences() {
2964 int depth = runtimeStack.frameCount();
2965 while (!elementArgumentReferences.isEmpty()
2966 && elementArgumentReferences.peek().ownerFrame == depth) {
2967 elementArgumentReferences.pop();
2968 }
2969 }
2970
2971 private Object resolveUserFunctionArgument(Object argument) {
2972 if (!(argument instanceof ArgumentReference)) {
2973 return argument;
2974 }
2975 ArgumentReference reference = (ArgumentReference) argument;
2976 Object snapshot = reference.snapshot();
2977 if (snapshot instanceof ArgumentReference) {
2978 return resolveUserFunctionArgument(snapshot);
2979 }
2980 return isUntyped(snapshot) ? reference : snapshot;
2981 }
2982
2983 private void resolveIndirectArguments(
2984 Object[] actualArgumentsParam,
2985 BuiltinFunction builtin) {
2986 for (int index = 0; index < actualArgumentsParam.length; index++) {
2987 boolean arrayArgument = builtin == BuiltinFunction.SPLIT && index == 1;
2988 actualArgumentsParam[index] = resolveIndirectArgument(
2989 actualArgumentsParam[index],
2990 arrayArgument,
2991 false);
2992 }
2993 }
2994
2995 private void resolveIndirectArguments(
2996 Object[] actualArgumentsParam,
2997 ExtensionFunction function) {
2998 boolean[] arrayArguments = new boolean[actualArgumentsParam.length];
2999 boolean[] rawValueArguments = new boolean[actualArgumentsParam.length];
3000 for (int index : function.collectAssocArrayIndexes(actualArgumentsParam.length)) {
3001 arrayArguments[index] = true;
3002 }
3003 for (int index : function.collectRawValueIndexes(actualArgumentsParam.length)) {
3004 rawValueArguments[index] = true;
3005 }
3006 for (int index = 0; index < actualArgumentsParam.length; index++) {
3007 actualArgumentsParam[index] = resolveIndirectArgument(
3008 actualArgumentsParam[index],
3009 arrayArguments[index],
3010 rawValueArguments[index]);
3011 }
3012 }
3013
3014 private Object resolveIndirectArgument(
3015 Object argument,
3016 boolean arrayArgument,
3017 boolean rawValueArgument) {
3018 if (!(argument instanceof ArgumentReference)) {
3019 return argument;
3020 }
3021 ArgumentReference reference = (ArgumentReference) argument;
3022 if (rawValueArgument) {
3023 return resolveRawArgumentReference(reference);
3024 }
3025 return resolveArgumentReference(reference, arrayArgument);
3026 }
3027
3028 private Object invokeIndirectBuiltin(
3029 BuiltinFunction builtin,
3030 Object[] args,
3031 int lineNumber) {
3032 switch (builtin) {
3033 case ATAN2:
3034 requireIndirectArgumentCount(builtin, args, 2, 2, lineNumber);
3035 return Math.atan2(JRT.toDouble(args[0]), JRT.toDouble(args[1]));
3036 case CLOSE:
3037 requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3038 return jrt.jrtClose(jrt.toAwkString(args[0]));
3039 case COS:
3040 requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3041 return Math.cos(JRT.toDouble(args[0]));
3042 case EXP:
3043 requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3044 return Math.exp(JRT.toDouble(args[0]));
3045 case GSUB:
3046 case SUB:
3047 requireIndirectArgumentCount(builtin, args, 2, 2, lineNumber);
3048 return substituteInputLine(
3049 builtin == BuiltinFunction.GSUB,
3050 args[0],
3051 args[1]);
3052 case INDEX:
3053 requireIndirectArgumentCount(builtin, args, 2, 2, lineNumber);
3054 return jrt.index(jrt.toAwkString(args[0]), jrt.toAwkString(args[1]));
3055 case INT:
3056 requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3057 return JRT.truncateToScalar(JRT.toDouble(args[0]));
3058 case LENGTH:
3059 requireIndirectArgumentCount(builtin, args, 0, 1, lineNumber);
3060 return args.length == 0 ? Integer.valueOf(jrt.jrtGetInputField(0).toString().length()) : lengthOf(args[0]);
3061 case LOG:
3062 requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3063 return Math.log(JRT.toDouble(args[0]));
3064 case MATCH:
3065 requireIndirectArgumentCount(builtin, args, 2, 2, lineNumber);
3066 return jrt.matchPosition(jrt.toAwkString(args[0]), jrt.toAwkString(args[1]));
3067 case RAND:
3068 requireIndirectArgumentCount(builtin, args, 0, 0, lineNumber);
3069 return randomNumberGenerator.nextDouble();
3070 case SIN:
3071 requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3072 return Math.sin(JRT.toDouble(args[0]));
3073 case SPLIT:
3074 requireIndirectArgumentCount(builtin, args, 2, 3, lineNumber);
3075 return splitIntoArray(
3076 args[0],
3077 args[1],
3078 args.length == 3 ? args[2] : jrt.getFSVar(),
3079 lineNumber);
3080 case SPRINTF:
3081 requireIndirectArgumentCount(builtin, args, 1, Integer.MAX_VALUE, lineNumber);
3082 return jrt
3083 .sprintf(
3084 jrt.toAwkString(args[0]),
3085 Arrays.copyOfRange(args, 1, args.length));
3086 case SQRT:
3087 requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3088 return Math.sqrt(JRT.toDouble(args[0]));
3089 case SRAND:
3090 requireIndirectArgumentCount(builtin, args, 0, 1, lineNumber);
3091 int seed = args.length == 0 ? JRT.timeSeed() : (int) JRT.toDouble(args[0]);
3092 int previousSeed = randomNumberGenerator.getSeed();
3093 randomNumberGenerator.setSeed(seed);
3094 return Integer.valueOf(previousSeed);
3095 case SUBSTR:
3096 requireIndirectArgumentCount(builtin, args, 2, 3, lineNumber);
3097 return substring(
3098 args[0],
3099 args[1],
3100 args.length == 3 ? args[2] : null);
3101 case SYSTEM:
3102 requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3103 return jrt.jrtSystem(jrt.toAwkString(args[0]));
3104 case TOLOWER:
3105 requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3106 return jrt.toAwkString(args[0]).toLowerCase();
3107 case TOUPPER:
3108 requireIndirectArgumentCount(builtin, args, 1, 1, lineNumber);
3109 return jrt.toAwkString(args[0]).toUpperCase();
3110 default:
3111 throw new AwkRuntimeException(
3112 lineNumber,
3113 "indirect calls are not implemented for builtin `" + builtin.getAwkName() + "'");
3114 }
3115 }
3116
3117 private void requireIndirectArgumentCount(
3118 BuiltinFunction builtin,
3119 Object[] args,
3120 int minimum,
3121 int maximum,
3122 int lineNumber) {
3123 if (args.length < minimum || args.length > maximum) {
3124 String expected = minimum == maximum ? Integer.toString(minimum) : minimum + " to " + maximum;
3125 throw new AwkRuntimeException(
3126 lineNumber,
3127 builtin.getAwkName() + " requires " + expected + " argument(s), not " + args.length);
3128 }
3129 }
3130
3131 private Object invokeExtension(
3132 ExtensionFunction function,
3133 Object[] args,
3134 int lineNumber) {
3135
3136 currentLineNumber = lineNumber;
3137 String extensionClassName = function.getExtensionClassName();
3138 JawkExtension extension = extensionInstances.get(extensionClassName);
3139 if (extension == null) {
3140 throw new AwkRuntimeException(
3141 lineNumber,
3142 "Extension instance for class '" + extensionClassName + "' is not registered");
3143 }
3144 if (!(extension instanceof AbstractExtension)) {
3145 throw new AwkRuntimeException(
3146 lineNumber,
3147 "Extension instance for class '" + extensionClassName
3148 + "' does not extend "
3149 + AbstractExtension.class.getName());
3150 }
3151 Map<IndirectArrayArgumentReference, Object> attachedValues = captureAttachedArrayArgumentValues();
3152 Object result;
3153 try {
3154 result = function.invoke((AbstractExtension) extension, args);
3155 } finally {
3156 detachReplacedArrayArgumentReferences(attachedValues);
3157 }
3158 if (result == null) {
3159 return "";
3160 }
3161 if (result instanceof Number
3162 || result instanceof String
3163 || result instanceof Map) {
3164 return result;
3165 }
3166 return jrt.toAwkString(result);
3167 }
3168
3169 private void execMatch() {
3170 String ere = jrt.toAwkString(pop());
3171 String s = jrt.toAwkString(pop());
3172 push(jrt.matchPosition(s, ere));
3173 }
3174
3175 private void execSubForDollar0(BooleanTuple tuple) {
3176 Object replacement = pop();
3177 Object ere = pop();
3178 push(substituteInputLine(tuple.getValue(), ere, replacement));
3179 }
3180
3181 private Object substituteInputLine(boolean global, Object ere, Object replacement) {
3182 String orig = jrt.toAwkString(jrt.jrtGetInputField(0));
3183 Object replacements = global ?
3184 jrt.replaceAll(orig, jrt.toAwkString(replacement), jrt.toAwkString(ere)) :
3185 jrt.replaceFirst(orig, jrt.toAwkString(replacement), jrt.toAwkString(ere));
3186 jrt.setInputLine(jrt.getReplaceResult());
3187 jrt.jrtParseFields();
3188 return replacements;
3189 }
3190
3191 private void execSubForDollarReference(BooleanTuple tuple) {
3192 boolean isGsub = tuple.getValue();
3193 long fieldNum = JRT.parseFieldNumber(pop());
3194 String orig = jrt.toAwkString(pop());
3195 String repl = jrt.toAwkString(pop());
3196 String ere = jrt.toAwkString(pop());
3197 push(isGsub ? jrt.replaceAll(orig, repl, ere) : jrt.replaceFirst(orig, repl, ere));
3198 String newstring = jrt.getReplaceResult();
3199 if (fieldNum == 0) {
3200 jrt.setInputLine(newstring);
3201 jrt.jrtParseFields();
3202 } else {
3203 jrt.jrtSetInputField(newstring, fieldNum);
3204 }
3205 }
3206
3207 private void execSubForVariable(SubstitutionVariableTuple tuple, PositionTracker position) {
3208 String newString = execSubOrGSub(tuple.isGlobalSubstitution());
3209 assign(tuple.getVariableOffset(), newString, tuple.isGlobal(), position, false);
3210 }
3211
3212 private void execSubForArrayReference(SubstitutionVariableTuple tuple) {
3213 Object arrIdx = pop();
3214 String newString = execSubOrGSub(tuple.isGlobalSubstitution());
3215 assignArray(tuple.getVariableOffset(), arrIdx, newString, tuple.isGlobal());
3216 pop();
3217 }
3218
3219 private void execSubForMapReference(BooleanTuple tuple) {
3220 Object arrIdx = pop();
3221 Map<Object, Object> array = toMap(pop());
3222 String newString = execSubOrGSub(tuple.getValue());
3223 assignMapElement(array, arrIdx, newString);
3224 pop();
3225 }
3226
3227 private void execSplit(CountTuple tuple, PositionTracker position) {
3228 long numArgs = tuple.getCount();
3229 Object fs;
3230 if (numArgs == 2) {
3231 fs = jrt.getFSVar();
3232 } else if (numArgs == 3) {
3233
3234
3235 fs = pop();
3236 } else {
3237 throw new Error("Invalid # of args. split() requires 2 or 3. Got: " + numArgs);
3238 }
3239 Object target = pop();
3240 Object source = pop();
3241 push(splitIntoArray(source, target, fs, position.lineNumber()));
3242 }
3243
3244 private Object splitIntoArray(Object source, Object target, Object separator, int lineNumber) {
3245 if (!(target instanceof Map)) {
3246 throw new AwkRuntimeException(lineNumber, target + " is not an array.");
3247 }
3248 Enumeration<Object> tokenizer = jrt.splitTokenizer(jrt.toAwkString(source), separator);
3249 @SuppressWarnings("unchecked")
3250 Map<Object, Object> assocArray = (Map<Object, Object>) target;
3251 assocArray.clear();
3252 detachMissingArrayArgumentReferences(assocArray);
3253 long cnt = 0;
3254 while (tokenizer.hasMoreElements()) {
3255 Object value = tokenizer.nextElement();
3256 assocArray.put(++cnt, jrt.toInputScalar(value));
3257 }
3258 return Long.valueOf(cnt);
3259 }
3260
3261 private void execSubstr(CountTuple tuple) {
3262 long numArgs = tuple.getCount();
3263 Object length = null;
3264 if (numArgs == 3) {
3265 length = pop();
3266 } else if (numArgs != 2) {
3267 throw new Error("numArgs for SUBSTR must be 2 or 3. It is " + numArgs);
3268 }
3269 Object start = pop();
3270 Object value = pop();
3271 push(substring(value, start, length));
3272 }
3273
3274 private Object substring(Object value, Object start, Object requestedLength) {
3275 String s = jrt.toAwkString(value);
3276 int startPos = (int) JRT.toDouble(start);
3277 if (startPos <= 0) {
3278 startPos = 1;
3279 }
3280 if (startPos > s.length()) {
3281 return BLANK;
3282 }
3283 int available = s.length() - startPos + 1;
3284
3285
3286
3287 long length = requestedLength == null ? available : JRT.toLong(requestedLength);
3288 if (length <= 0) {
3289 return BLANK;
3290 }
3291 if (length > available) {
3292 length = available;
3293 }
3294 return s.substring(startPos - 1, startPos - 1 + (int) length);
3295 }
3296
3297 private void execSetNumGlobals(CountTuple tuple) {
3298 long numGlobals = tuple.getCount();
3299 Object[] globals = runtimeStack.getNumGlobals();
3300 if (mergedGlobalLayoutActive) {
3301 if (!hasCompatiblePersistentGlobalLayout(numGlobals)) {
3302 throw new IllegalStateException(
3303 "AVM globals are already initialized for an incompatible persistent layout.");
3304 }
3305 applyExecutionInitialVariablesToGlobalSlots(true);
3306 } else if (globals == null) {
3307 runtimeStack.setNumGlobals(numGlobals, globalVariableOffsets);
3308 initializedEvalGlobalVariableOffsets = globalVariableOffsets;
3309 initializedEvalGlobalVariableArrays = globalVariableArrays;
3310 applyExecutionInitialVariablesToGlobalSlots(false);
3311 } else if (!hasCompatibleEvalGlobalLayout(numGlobals)) {
3312 throw new IllegalStateException(
3313 "AVM globals are already initialized for a different eval layout. Call prepareForEval(...) first.");
3314 }
3315 }
3316
3317 private void populateEnviron(long offset) {
3318 environOffset = offset;
3319 for (Map.Entry<String, String> var : System.getenv().entrySet()) {
3320 assignArray(environOffset, var.getKey(), jrt.toInputScalar(var.getValue()), true);
3321 pop();
3322 }
3323 }
3324
3325 private void populateArgc(long offset) {
3326 argcOffset = offset;
3327
3328 runtimeStack.setVariable(argcOffset, Integer.valueOf(arguments.size() + 1), true);
3329 }
3330
3331 private void populateArgv(long offset) {
3332 argvOffset = offset;
3333
3334
3335 Object existing = runtimeStack.getVariable(argvOffset, true);
3336 if (existing instanceof Map && !((Map<?, ?>) existing).isEmpty()) {
3337 return;
3338 }
3339 forEachArgvEntry((index, value) -> {
3340 assignArray(argvOffset, index, value, true);
3341 pop();
3342 });
3343 }
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354 private void forEachArgvEntry(BiConsumer<Long, Object> consumer) {
3355 consumer.accept(Long.valueOf(0L), "jawk");
3356 for (int i = 1; i <= arguments.size(); i++) {
3357 consumer.accept(Long.valueOf(i), jrt.toInputScalar(arguments.get(i - 1)));
3358 }
3359 }
3360
3361
3362
3363
3364
3365
3366 private void runBeforeStartHooks() {
3367 if (beforeStartHooksExecuted || extensionInstances.isEmpty()) {
3368 return;
3369 }
3370 beforeStartHooksExecuted = true;
3371 Set<JawkExtension> started = new LinkedHashSet<JawkExtension>();
3372 for (JawkExtension extension : extensionInstances.values()) {
3373 if (started.add(extension)) {
3374 extension.beforeStart(this, jrt);
3375 }
3376 }
3377 }
3378
3379
3380
3381
3382
3383
3384
3385
3386 private void execUpdateSymtab(long offset) {
3387 symtabOffset = offset;
3388 if (runtimeStack.getVariable(offset, true) != null) {
3389
3390 return;
3391 }
3392 SymtabArray symtab = new SymtabArray();
3393 for (String name : executionInitialVariables.keySet()) {
3394 symtab.put(name, getVariable(name));
3395 }
3396 for (String name : getGlobalVariableNames()) {
3397
3398 if ("SYMTAB".equals(name) || "FUNCTAB".equals(name)) {
3399 continue;
3400 }
3401 symtab.put(name, getVariable(name));
3402 }
3403
3404
3405 for (String name : getSpecialVariableNames()) {
3406 symtab.put(name, getVariable(name));
3407 }
3408 symtab.activate();
3409 runtimeStack.setVariable(offset, symtab, true);
3410 }
3411
3412 private final class SymtabArray extends java.util.AbstractMap<Object, Object> implements AssocArray {
3413 private final Map<Object, Object> entries = newAwkArray();
3414 private final Set<String> assignableNames = new HashSet<String>();
3415 private boolean active;
3416
3417 private void activate() {
3418 active = true;
3419 }
3420
3421
3422 @Override
3423 public Object get(Object key) {
3424 String name = key == null ? "" : key.toString();
3425 if (active && !isMetaTableName(name) && isLiveSpecialVariable(name)) {
3426 return getVariable(name);
3427 }
3428 Integer offset = globalVariableOffsets == null ? null : globalVariableOffsets.get(name);
3429 if (active && !isMetaTableName(name) && offset != null) {
3430 return runtimeStack.getVariable(offset.intValue(), true);
3431 }
3432 return entries.get(key);
3433 }
3434
3435
3436 @Override
3437 public boolean containsKey(Object key) {
3438 String name = key == null ? "" : key.toString();
3439 return active
3440 && !isMetaTableName(name)
3441 && (isLiveSpecialVariable(name)
3442 || (globalVariableOffsets != null && globalVariableOffsets.containsKey(name)))
3443 || entries.containsKey(key);
3444 }
3445
3446
3447 @Override
3448 public Object put(Object key, Object value) {
3449 String name = key == null ? "" : key.toString();
3450 if (!active) {
3451 if (key != null) {
3452 assignableNames.add(name);
3453 }
3454 return entries.put(key, value);
3455 }
3456 if (!assignableNames.contains(name)) {
3457 throw new AwkRuntimeException("Cannot assign to an arbitrary element of SYMTAB.");
3458 }
3459 validateGlobalType(name, value);
3460 Object previous = entries.put(key, value);
3461 if (isMetaTableName(name)) {
3462 return previous;
3463 }
3464 if (applyLiveSpecialVariable(name, value)) {
3465 return previous;
3466 }
3467 Integer offset = globalVariableOffsets == null ? null : globalVariableOffsets.get(name);
3468 if (offset != null) {
3469 runtimeStack.setVariable(offset.intValue(), value, true);
3470 }
3471 return previous;
3472 }
3473
3474 private Object putRuntimeVariable(String name, Object value) {
3475 assignableNames.add(name);
3476 return put(name, value);
3477 }
3478
3479 private boolean applyLiveSpecialVariable(String name, Object value) {
3480 if ("ARGC".equals(name)) {
3481 if (argcOffset == NULL_OFFSET) {
3482 throw new AwkRuntimeException("ARGC is read-only (not materialized).");
3483 }
3484 runtimeStack.setVariable(argcOffset, value, true);
3485 return true;
3486 }
3487 if ("RSTART".equals(name)) {
3488 jrt.setRSTART(value);
3489 return true;
3490 }
3491 if ("RLENGTH".equals(name)) {
3492 jrt.setRLENGTH(value);
3493 return true;
3494 }
3495 return isManagedSpecialVariable(name) && jrt.applySpecialVariable(name, value);
3496 }
3497
3498 private boolean isLiveSpecialVariable(String name) {
3499 return isManagedSpecialVariable(name)
3500 || "RSTART".equals(name)
3501 || "RLENGTH".equals(name);
3502 }
3503
3504
3505 @Override
3506 public Object remove(Object key) {
3507 if (active) {
3508 throw new AwkRuntimeException("Cannot delete an element from SYMTAB.");
3509 }
3510 return entries.remove(key);
3511 }
3512
3513
3514 @Override
3515 public void clear() {
3516 if (active) {
3517 throw new AwkRuntimeException("Cannot delete SYMTAB.");
3518 }
3519 entries.clear();
3520 }
3521
3522 private void validateGlobalType(String name, Object value) {
3523 Boolean array = globalVariableArrays == null ? null : globalVariableArrays.get(name);
3524 if (Boolean.TRUE.equals(array) && !(value instanceof Map)) {
3525 throw new AwkRuntimeException(
3526 "Attempting to use array `" + name + "' in a scalar context.");
3527 }
3528 if (Boolean.FALSE.equals(array) && value instanceof Map) {
3529 throw new AwkRuntimeException(
3530 "Attempting to use scalar `" + name + "' as an array.");
3531 }
3532 }
3533
3534
3535 @Override
3536 public Set<Map.Entry<Object, Object>> entrySet() {
3537 return new AbstractSet<Map.Entry<Object, Object>>() {
3538
3539 @Override
3540 public Iterator<Map.Entry<Object, Object>> iterator() {
3541 final Iterator<Map.Entry<Object, Object>> iterator = entries.entrySet().iterator();
3542 return new Iterator<Map.Entry<Object, Object>>() {
3543
3544 @Override
3545 public boolean hasNext() {
3546 return iterator.hasNext();
3547 }
3548
3549 @Override
3550 public Map.Entry<Object, Object> next() {
3551 return new LiveSymtabEntry(iterator.next().getKey());
3552 }
3553
3554 @Override
3555 public void remove() {
3556 if (active) {
3557 throw new AwkRuntimeException("Cannot delete an element from SYMTAB.");
3558 }
3559 iterator.remove();
3560 }
3561 };
3562 }
3563
3564 @Override
3565 public int size() {
3566 return entries.size();
3567 }
3568 };
3569 }
3570
3571 private boolean isMetaTableName(String name) {
3572 return "SYMTAB".equals(name) || "FUNCTAB".equals(name);
3573 }
3574
3575 private final class LiveSymtabEntry implements Map.Entry<Object, Object> {
3576
3577 private final Object key;
3578
3579 private LiveSymtabEntry(Object key) {
3580 this.key = key;
3581 }
3582
3583 @Override
3584 public Object getKey() {
3585 return key;
3586 }
3587
3588 @Override
3589 public Object getValue() {
3590 return SymtabArray.this.get(key);
3591 }
3592
3593 @Override
3594 public Object setValue(Object value) {
3595 return SymtabArray.this.put(key, value);
3596 }
3597
3598 @Override
3599 public boolean equals(Object obj) {
3600 if (!(obj instanceof Map.Entry)) {
3601 return false;
3602 }
3603 Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
3604 return Objects.equals(key, other.getKey()) && Objects.equals(getValue(), other.getValue());
3605 }
3606
3607 @Override
3608 public int hashCode() {
3609 return Objects.hashCode(key) ^ Objects.hashCode(getValue());
3610 }
3611 }
3612 }
3613
3614
3615
3616
3617
3618
3619
3620 private void execUpdateFunctab(long offset) {
3621 if (runtimeStack.getVariable(offset, true) != null) {
3622 return;
3623 }
3624 Map<Object, Object> functab = newAwkArray();
3625 for (String name : BuiltinFunction.names()) {
3626 functab.put(name, name);
3627 }
3628 for (String name : functionNames) {
3629 functab.put(name, name);
3630 }
3631 Set<JawkExtension> seen = new LinkedHashSet<JawkExtension>();
3632 for (JawkExtension extension : extensionInstances.values()) {
3633 if (seen.add(extension)) {
3634 for (String keyword : extension.getExtensionFunctions().keySet()) {
3635 functab.put(keyword, keyword);
3636 }
3637 }
3638 }
3639 runtimeStack.setVariable(offset, new ReadOnlyArray("FUNCTAB", functab), true);
3640 }
3641
3642 private static final class ReadOnlyArray extends java.util.AbstractMap<Object, Object> implements AssocArray {
3643 private final String name;
3644 private final Map<Object, Object> entries;
3645
3646 private ReadOnlyArray(String nameParam, Map<Object, Object> entriesParam) {
3647 name = nameParam;
3648 entries = entriesParam;
3649 }
3650
3651
3652 @Override
3653 public Object get(Object key) {
3654 return JRT.containsAwkKey(entries, key) ? entries.get(key) : BLANK;
3655 }
3656
3657
3658 @Override
3659 public boolean containsKey(Object key) {
3660 return JRT.containsAwkKey(entries, key);
3661 }
3662
3663
3664 @Override
3665 public Set<Map.Entry<Object, Object>> entrySet() {
3666 return Collections.unmodifiableMap(entries).entrySet();
3667 }
3668
3669
3670 @Override
3671 public Object put(Object key, Object value) {
3672 throw readOnlyError();
3673 }
3674
3675
3676 @Override
3677 public Object remove(Object key) {
3678 throw readOnlyError();
3679 }
3680
3681
3682 @Override
3683 public void clear() {
3684 throw readOnlyError();
3685 }
3686
3687 private AwkRuntimeException readOnlyError() {
3688 return new AwkRuntimeException(name + " is read-only.");
3689 }
3690 }
3691
3692
3693 private void updateSymtabEntry(String name, Object value) {
3694 if (symtabOffset == NULL_OFFSET) {
3695 return;
3696 }
3697 Object symtab = runtimeStack.getVariable(symtabOffset, true);
3698 if (symtab instanceof SymtabArray) {
3699 ((SymtabArray) symtab).putRuntimeVariable(name, value);
3700 } else if (symtab instanceof Map) {
3701 @SuppressWarnings("unchecked")
3702 Map<Object, Object> symtabMap = (Map<Object, Object>) symtab;
3703 symtabMap.put(name, value);
3704 }
3705 }
3706
3707 private void execApplySubsep(CountTuple tuple) {
3708 long count = tuple.getCount();
3709 if (count == 1) {
3710 Object value = pop();
3711 checkScalar(value);
3712
3713
3714
3715
3716
3717 push(AssocArray.isIntegralNumberKey(value) ? value : jrt.toAwkString(value));
3718 return;
3719 }
3720 StringBuilder sb = new StringBuilder();
3721 Object value = pop();
3722 checkScalar(value);
3723 sb.append(jrt.toAwkString(value));
3724 String subsep = jrt.toAwkString(jrt.getSUBSEPVar());
3725 for (int i = 1; i < count; i++) {
3726 sb.insert(0, subsep);
3727 value = pop();
3728 checkScalar(value);
3729 sb.insert(0, jrt.toAwkString(value));
3730 }
3731 push(sb.toString());
3732 }
3733
3734 private long beforeProfiledTuple(Tuple tuple, Opcode opcode) {
3735 long now = System.nanoTime();
3736 if (opcode == Opcode.CALL_FUNCTION) {
3737 CallFunctionTuple callTuple = (CallFunctionTuple) tuple;
3738 activeProfilingFunctions.push(new ActiveFunction(callTuple.getFunctionName(), now));
3739 } else if (opcode == Opcode.EXTENSION) {
3740 ExtensionTuple extensionTuple = (ExtensionTuple) tuple;
3741 ExtensionFunction function = extensionTuple.getFunction();
3742 activeProfilingFunctions.push(new ActiveFunction(function.getKeyword(), now));
3743 }
3744 return now;
3745 }
3746
3747 private void afterProfiledTuple(Opcode opcode, long tupleStartNanos) {
3748 long now = System.nanoTime();
3749 statisticsFor(tupleProfilingStats, opcode).add(now - tupleStartNanos);
3750 if (opcode == Opcode.EXIT_WITH_CODE || opcode == Opcode.EXIT_WITHOUT_CODE) {
3751 recordAllFunctionExits(now);
3752 } else if (opcode == Opcode.EXTENSION || opcode == Opcode.RETURN_FROM_FUNCTION) {
3753 recordFunctionExit(now);
3754 }
3755 }
3756
3757 private static <K> ProfilingReport.Accumulator statisticsFor(
3758 Map<K, ProfilingReport.Accumulator> stats,
3759 K key) {
3760 ProfilingReport.Accumulator accumulator = stats.get(key);
3761 if (accumulator == null) {
3762 accumulator = new ProfilingReport.Accumulator();
3763 stats.put(key, accumulator);
3764 }
3765 return accumulator;
3766 }
3767
3768 private void recordFunctionExit(long now) {
3769 if (activeProfilingFunctions.isEmpty()) {
3770 return;
3771 }
3772 ActiveFunction function = activeProfilingFunctions.pop();
3773 statisticsFor(functionProfilingStats, function.name).add(now - function.startNanos);
3774 }
3775
3776 private void recordAllFunctionExits(long now) {
3777 while (!activeProfilingFunctions.isEmpty()) {
3778 recordFunctionExit(now);
3779 }
3780 }
3781
3782 private static final class ActiveFunction {
3783 private final String name;
3784 private final long startNanos;
3785
3786 private ActiveFunction(String name, long startNanos) {
3787 this.name = name;
3788 this.startNanos = startNanos;
3789 }
3790 }
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803 @Override
3804 public void close() throws IOException {
3805 jrt.jrtCloseAll();
3806 closeResolvedInputSource();
3807 resolvedInputSource = null;
3808 }
3809
3810
3811
3812
3813
3814
3815
3816 private void closeResolvedInputSource() {
3817 closeInputSource(resolvedInputSource);
3818 }
3819
3820 private void closeInputSource(InputSource inputSource) {
3821 if (!(inputSource instanceof Closeable)) {
3822 return;
3823 }
3824 try {
3825 ((Closeable) inputSource).close();
3826 } catch (IOException ignored) {
3827
3828 }
3829 }
3830
3831 private Object[] popArguments(long numArgs) {
3832 int count = (int) numArgs;
3833 Object[] args = new Object[count];
3834 operandStackSize -= count;
3835 System.arraycopy(operandStack, operandStackSize, args, 0, count);
3836 Arrays.fill(operandStack, operandStackSize, operandStackSize + count, null);
3837 return args;
3838 }
3839
3840
3841
3842
3843 private String sprintfFunction(long numArgs) {
3844 Object[] argArray = popArguments(numArgs - 1);
3845 String fmt = jrt.toAwkString(pop());
3846 return jrt.sprintf(fmt, argArray);
3847 }
3848
3849 private void setNumOnJRT(long fieldNum, Object num) {
3850
3851
3852
3853 if (fieldNum == 0) {
3854 jrt.setInputLine(jrt.toAwkString(num));
3855 jrt.jrtParseFields();
3856 } else {
3857 jrt.jrtSetInputField(num, fieldNum);
3858 }
3859 }
3860
3861 private String execSubOrGSub(boolean isGsub) {
3862 String newString;
3863
3864
3865
3866
3867 String orig = jrt.toAwkString(pop());
3868 String repl = jrt.toAwkString(pop());
3869 String ere = jrt.toAwkString(pop());
3870 push(isGsub ? jrt.replaceAll(orig, repl, ere) : jrt.replaceFirst(orig, repl, ere));
3871 newString = jrt.getReplaceResult();
3872
3873 return newString;
3874 }
3875
3876
3877
3878
3879 private void assign(long l, Object value, boolean isGlobal, PositionTracker position, boolean push) {
3880 value = JRT.untypedToBlank(value);
3881 checkScalar(value);
3882
3883 if (resolveVariable(l, isGlobal, false) instanceof Map) {
3884 throw new AwkRuntimeException(position.lineNumber(), "Attempting to use an array in a scalar context.");
3885 }
3886 if (push) {
3887 push(value);
3888 }
3889 runtimeStack.setVariable(l, value, isGlobal);
3890
3891 }
3892
3893
3894
3895
3896 private void assignArray(long offset, Object arrIdx, Object rhs, boolean isGlobal) {
3897 assignMapElement(ensureMapVariable(offset, isGlobal), arrIdx, rhs);
3898 }
3899
3900 private void assignMapElement(Map<Object, Object> array, Object arrIdx, Object rhs) {
3901 checkScalar(arrIdx);
3902 rhs = JRT.untypedToBlank(rhs);
3903 checkScalar(rhs);
3904 if (JRT.containsAwkKey(array, arrIdx)
3905 && JRT.getAssocArrayValue(array, arrIdx) instanceof Map) {
3906 throw new AwkRuntimeException("Attempting to use an array in a scalar context.");
3907 }
3908 array.put(arrIdx, rhs);
3909 push(rhs);
3910 }
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921 private static Object blankToZero(Object o) {
3922 return o == null || o instanceof UninitializedObject ? ZERO : o;
3923 }
3924
3925
3926
3927
3928
3929 private static Object numericValueOf(Object o) {
3930 if (o instanceof Long || o instanceof Integer || o instanceof Double) {
3931 return o;
3932 }
3933 return JRT.toDouble(o);
3934 }
3935
3936 private Object inc(long l, boolean isGlobal) {
3937 Object o = resolveVariable(l, isGlobal, false);
3938 if (o instanceof UninitializedObject) {
3939 o = ZERO;
3940 runtimeStack.setVariable(l, o, isGlobal);
3941 }
3942 Object updated = JRT.inc(o);
3943 runtimeStack.setVariable(l, updated, isGlobal);
3944 return o;
3945 }
3946
3947
3948
3949
3950
3951 private Object dec(long l, boolean isGlobal) {
3952 Object o = resolveVariable(l, isGlobal, false);
3953 if (o instanceof UninitializedObject) {
3954 o = ZERO;
3955 runtimeStack.setVariable(l, o, isGlobal);
3956 }
3957 Object updated = JRT.dec(o);
3958 runtimeStack.setVariable(l, updated, isGlobal);
3959 return o;
3960 }
3961
3962
3963 @Override
3964 public final Object getRS() {
3965 return jrt.getRSVar();
3966 }
3967
3968
3969 @Override
3970 public final Object getOFS() {
3971 return jrt.getOFSVar();
3972 }
3973
3974
3975 @Override
3976 public final Object getORS() {
3977 return jrt.getORSVar();
3978 }
3979
3980
3981 @Override
3982 public final Object getSUBSEP() {
3983 return jrt.getSUBSEPVar();
3984 }
3985
3986
3987
3988
3989
3990
3991
3992
3993 public Set<String> getGlobalVariableNames() {
3994 return globalVariableOffsets == null ?
3995 Collections.<String>emptySet() : Collections.unmodifiableSet(globalVariableOffsets.keySet());
3996 }
3997
3998
3999
4000
4001
4002
4003
4004 public Set<String> getFunctionNames() {
4005 return functionNames == null ? Collections.<String>emptySet() : Collections.unmodifiableSet(functionNames);
4006 }
4007
4008
4009
4010
4011
4012
4013 private static final Set<String> SPECIAL_VARIABLE_NAMES = Collections
4014 .unmodifiableSet(
4015 new LinkedHashSet<String>(
4016 Arrays
4017 .asList(
4018 "FS",
4019 "RS",
4020 "OFS",
4021 "ORS",
4022 "FILENAME",
4023 "SUBSEP",
4024 "CONVFMT",
4025 "OFMT",
4026 "NF",
4027 "NR",
4028 "FNR",
4029 "RSTART",
4030 "RLENGTH",
4031 "IGNORECASE",
4032 "ERRNO",
4033 "ARGIND",
4034 "ARGC",
4035 "ARGV")));
4036
4037
4038
4039
4040
4041
4042
4043 public Set<String> getSpecialVariableNames() {
4044 return SPECIAL_VARIABLE_NAMES;
4045 }
4046
4047
4048 @Override
4049 public final Object getVariable(String name) {
4050 if (name == null) {
4051 return null;
4052 }
4053 switch (name) {
4054 case "FS":
4055 return getFS();
4056 case "RS":
4057 return getRS();
4058 case "OFS":
4059 return getOFS();
4060 case "ORS":
4061 return getORS();
4062 case "FILENAME":
4063 return jrt.getFILENAME();
4064 case "SUBSEP":
4065 return getSUBSEP();
4066 case "CONVFMT":
4067 return getCONVFMT();
4068 case "OFMT":
4069 return jrt.getOFMTString();
4070 case "NF":
4071 return jrt.getNF();
4072 case "NR":
4073 return jrt.getNR();
4074 case "FNR":
4075 return jrt.getFNR();
4076 case "RSTART":
4077 return jrt.getRSTART();
4078 case "RLENGTH":
4079 return jrt.getRLENGTH();
4080 case "IGNORECASE":
4081 return jrt.getIGNORECASEVar();
4082 case "ERRNO":
4083 if (isManagedSpecialVariable(name)) {
4084 return jrt.getERRNO();
4085 }
4086
4087 break;
4088 case "ARGIND":
4089 if (isManagedSpecialVariable(name)) {
4090 return jrt.getARGIND();
4091 }
4092
4093 break;
4094
4095 case "ARGC":
4096 return getARGC();
4097 case "ARGV":
4098 return getARGV();
4099 default:
4100 break;
4101 }
4102 if (globalVariableOffsets == null) {
4103 return executionInitialVariables.get(name);
4104 }
4105 Integer offsetObj = globalVariableOffsets.get(name);
4106 if (offsetObj != null) {
4107 return runtimeStack.getVariable(offsetObj.intValue(), true);
4108 }
4109
4110
4111
4112 return executionInitialVariables == null ? null : executionInitialVariables.get(name);
4113 }
4114
4115
4116
4117
4118
4119
4120
4121 public String getSourceDescription() {
4122 return sourceDescription;
4123 }
4124
4125
4126
4127
4128
4129
4130
4131 public int getCurrentLineNumber() {
4132 return currentLineNumber;
4133 }
4134
4135
4136 @Override
4137 public final void assignVariable(String name, Object obj) {
4138
4139
4140 if (globalVariableOffsets == null || globalVariableArrays == null) {
4141 Object normalized = normalizeExternalVariableValue(obj);
4142 baseInitialVariables.put(name, normalized);
4143 if (isManagedSpecialVariable(name)) {
4144 baseSpecialVariables.put(name, normalized);
4145 }
4146 return;
4147 }
4148
4149
4150 if (functionNames.contains(name)) {
4151 throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + name + ").");
4152 }
4153
4154 Object normalized = normalizeExternalVariableValue(obj);
4155
4156
4157
4158
4159 if (!"ARGC".equals(name) && isManagedSpecialVariable(name)) {
4160 jrt.applySpecialVariable(name, normalized);
4161 updateSymtabEntry(name, normalized);
4162 return;
4163 }
4164
4165 Integer offsetObj = globalVariableOffsets.get(name);
4166 Boolean arrayObj = globalVariableArrays.get(name);
4167
4168 if (offsetObj != null) {
4169 if (arrayObj.booleanValue() && !(normalized instanceof Map)) {
4170 throw new IllegalArgumentException(
4171 "Cannot assign a scalar to a non-scalar variable (" + name + ").");
4172 }
4173 runtimeStack.setFilelistVariable(offsetObj.intValue(), normalized);
4174 } else if (runtimeStack.hasGlobalVariable(name)) {
4175 runtimeStack.setGlobalVariable(name, normalized);
4176 }
4177
4178 updateSymtabEntry(name, normalized);
4179 }
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197 private void executeNextfile(PositionTracker position) {
4198 if (nextFileAddress == null || !inputFileLoopStarted) {
4199 throw new AwkRuntimeException(
4200 position.lineNumber(),
4201 "`nextfile' cannot be called from a BEGIN rule");
4202 }
4203 if (withinEndBlocks) {
4204 throw new AwkRuntimeException(
4205 position.lineNumber(),
4206 "`nextfile' cannot be called from an END rule");
4207 }
4208 if (withinEndFileBlocks) {
4209 throw new AwkRuntimeException(
4210 position.lineNumber(),
4211 "`nextfile' cannot be called from an ENDFILE rule");
4212 }
4213
4214 resetCallState();
4215 if (endFileAddress == null
4216 || withinBeginFileBlocks && jrt.hasPendingInputFileError(resolvedInputSource)) {
4217
4218
4219
4220 withinBeginFileBlocks = false;
4221 position.jump(nextFileAddress);
4222 } else {
4223 withinBeginFileBlocks = false;
4224 withinEndFileBlocks = true;
4225 position.jump(endFileAddress);
4226 }
4227 }
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247 private void executeNext(PositionTracker position) {
4248 if (withinEndBlocks) {
4249 throw new AwkRuntimeException(
4250 position.lineNumber(),
4251 "`next' cannot be called from a `END' rule");
4252 }
4253 if (withinBeginFileBlocks) {
4254 throw new AwkRuntimeException(
4255 position.lineNumber(),
4256 "`next' cannot be called from a `BEGINFILE' rule");
4257 }
4258 if (withinEndFileBlocks) {
4259 throw new AwkRuntimeException(
4260 position.lineNumber(),
4261 "`next' cannot be called from a `ENDFILE' rule");
4262 }
4263 if (nextAddress == null || !mainInputLoopStarted) {
4264 throw new AwkRuntimeException(
4265 position.lineNumber(),
4266 "`next' cannot be called from a `BEGIN' rule");
4267 }
4268
4269 resetCallState();
4270 position.jump(nextAddress);
4271 }
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285 private boolean isMainInputFileBounded() {
4286 return endFileAddress != null && inputFileLoopStarted && !withinEndBlocks;
4287 }
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303 private void checkGetlineAllowed(PositionTracker position) {
4304 if (withinBeginFileBlocks || withinEndFileBlocks) {
4305 throw new AwkRuntimeException(
4306 position.lineNumber(),
4307 "non-redirected `getline' invalid inside `"
4308 + (withinBeginFileBlocks ? "BEGINFILE" : "ENDFILE")
4309 + "' rule");
4310 }
4311 }
4312
4313
4314 @Override
4315 public Object getFS() {
4316 return jrt.getFSVar();
4317 }
4318
4319
4320 @Override
4321 public Object getCONVFMT() {
4322 return jrt.getCONVFMTString();
4323 }
4324
4325
4326 @Override
4327 public void resetFNR() {
4328 jrt.setFNR(0);
4329 }
4330
4331
4332 @Override
4333 public void incFNR() {
4334 long v = jrt.getFNR();
4335 jrt.setFNR(v + 1);
4336 }
4337
4338
4339 @Override
4340 public void incNR() {
4341 long v = jrt.getNR();
4342 jrt.setNR(v + 1);
4343 }
4344
4345
4346 @Override
4347 public void setNF(Integer newNf) {
4348 jrt.setNF(newNf);
4349 }
4350
4351
4352 @Override
4353 public void setFILENAME(String filename) {
4354 jrt.setFILENAMEViaJrt(jrt.toInputScalar(filename));
4355 }
4356
4357
4358 @Override
4359 public Object getARGV() {
4360 if (argvOffset == NULL_OFFSET) {
4361 Map<Object, Object> argv = newAwkArray();
4362 forEachArgvEntry(argv::put);
4363 return argv;
4364 }
4365 return runtimeStack.getVariable(argvOffset, true);
4366 }
4367
4368
4369 @Override
4370 public Object getARGC() {
4371 if (argcOffset == NULL_OFFSET) {
4372 return Long.valueOf(arguments.size() + 1);
4373 }
4374 return runtimeStack.getVariable(argcOffset, true);
4375 }
4376
4377 private String getOFMT() {
4378 return jrt.getOFMTString();
4379 }
4380
4381 private Map<Object, Object> newAwkArray() {
4382 return JRT.createAwkMap(sortedArrayKeys);
4383 }
4384
4385 private Map<Object, Object> ensureMapVariable(long offset, boolean isGlobal) {
4386 return toMap(resolveVariable(offset, isGlobal, true));
4387 }
4388
4389 private Map<Object, Object> getMapVariable(long offset, boolean isGlobal) {
4390 return toMap(resolveVariable(offset, isGlobal, true));
4391 }
4392
4393 private Object resolveVariable(long offset, boolean isGlobal, boolean arrayContext) {
4394 Object value = runtimeStack.getVariable(offset, isGlobal);
4395
4396
4397 if (!isGlobal && value instanceof ArgumentReference) {
4398 value = resolveArgumentReference((ArgumentReference) value, arrayContext);
4399 runtimeStack.setVariable(offset, value, isGlobal);
4400 return value;
4401 }
4402 if (!isUntyped(value)) {
4403 return value;
4404 }
4405 value = arrayContext ? newAwkArray() : BLANK;
4406 runtimeStack.setVariable(offset, value, isGlobal);
4407 return value;
4408 }
4409
4410 private Object resolveRawArgumentReference(ArgumentReference reference) {
4411 Object currentValue = readCurrentArgumentValue(reference);
4412 if (currentValue instanceof Map
4413 || currentValue instanceof UninitializedObject
4414 && !(currentValue instanceof UntypedObject)) {
4415 return currentValue;
4416 }
4417 Object value = reference.snapshot();
4418 return value instanceof ArgumentReference ?
4419 resolveRawArgumentReference((ArgumentReference) value) : value;
4420 }
4421
4422 private Object readCurrentArgumentValue(ArgumentReference reference) {
4423 Object value = reference.currentValue();
4424 return value instanceof ArgumentReference ?
4425 readCurrentArgumentValue((ArgumentReference) value) : value;
4426 }
4427
4428 private Object resolveArgumentReference(ArgumentReference reference, boolean arrayContext) {
4429 if (!arrayContext) {
4430 checkScalar(readCurrentArgumentValue(reference));
4431 }
4432 Object value = arrayContext ? reference.currentValue() : reference.snapshot();
4433 if (value instanceof ArgumentReference) {
4434 value = resolveArgumentReference((ArgumentReference) value, arrayContext);
4435 if (arrayContext) {
4436 reference.setValue(value);
4437 } else {
4438 reference.setScalarValue(value);
4439 }
4440 return value;
4441 }
4442 if (!isUntyped(value)) {
4443 return value;
4444 }
4445 value = arrayContext ? newAwkArray() : BLANK;
4446 if (arrayContext) {
4447 reference.setValue(value);
4448 } else {
4449 reference.setScalarValue(value);
4450 }
4451 return value;
4452 }
4453
4454 private Object resolveLengthArgumentReference(ArgumentReference reference) {
4455 Object currentValue = readCurrentArgumentValue(reference);
4456 return currentValue instanceof Map ? currentValue : resolveArgumentReference(reference, false);
4457 }
4458
4459 private Map<IndirectArrayArgumentReference, Object> captureAttachedArrayArgumentValues() {
4460 if (elementArgumentReferences.isEmpty()) {
4461 return Collections.emptyMap();
4462 }
4463 Map<IndirectArrayArgumentReference, Object> values = new IdentityHashMap<IndirectArrayArgumentReference, Object>();
4464 for (IndirectArrayArgumentReference reference : elementArgumentReferences) {
4465 if (reference.isAttached()) {
4466 values.put(reference, reference.currentValue());
4467 }
4468 }
4469 return values;
4470 }
4471
4472 private void detachReplacedArrayArgumentReferences(
4473 Map<IndirectArrayArgumentReference, Object> attachedValues) {
4474 for (Map.Entry<IndirectArrayArgumentReference, Object> entry : attachedValues.entrySet()) {
4475 entry.getKey().detachIfReplaced(entry.getValue());
4476 }
4477 }
4478
4479 private void detachMissingArrayArgumentReferences(Map<Object, Object> map) {
4480 if (elementArgumentReferences.isEmpty()) {
4481 return;
4482 }
4483 for (IndirectArrayArgumentReference reference : elementArgumentReferences) {
4484 reference.detachIfMissing(map);
4485 }
4486 }
4487
4488 private static boolean isUntyped(Object value) {
4489 return value == null || value instanceof UntypedObject;
4490 }
4491
4492
4493
4494
4495
4496
4497
4498
4499 private Map<Object, Object> toMap(Object value) {
4500 if (!(value instanceof Map)) {
4501 throw new AwkRuntimeException("Attempting to use a scalar as an array.");
4502 }
4503 @SuppressWarnings("unchecked")
4504 Map<Object, Object> map = (Map<Object, Object>) value;
4505 return map;
4506 }
4507
4508
4509
4510
4511
4512
4513
4514
4515 private void checkScalar(Object value) {
4516 if (value instanceof Map) {
4517 throw new AwkRuntimeException("Attempting to use an array in a scalar context.");
4518 }
4519 }
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531 private Map<Object, Object> ensureArrayInArray(Map<Object, Object> map, Object key) {
4532 checkScalar(key);
4533 boolean existingKey = JRT.containsAwkKey(map, key);
4534 Object value = JRT.getAssocArrayValue(map, key);
4535 if (!existingKey || value == null || value instanceof UntypedObject) {
4536 Map<Object, Object> nested = newAwkArray();
4537 map.put(key, nested);
4538 return nested;
4539 }
4540 if (!(value instanceof Map)) {
4541 throw new AwkRuntimeException("Attempting to use a scalar as an array.");
4542 }
4543 @SuppressWarnings("unchecked")
4544 Map<Object, Object> nested = (Map<Object, Object>) value;
4545 return nested;
4546 }
4547
4548 private Object normalizeExternalVariableValue(Object value) {
4549 if (value instanceof String) {
4550 return jrt.toInputScalar(value);
4551 }
4552 if (!(value instanceof Map) && !(value instanceof List)) {
4553 return value;
4554 }
4555 return AssocArray.normalizeValue(value, sortedArrayKeys);
4556 }
4557
4558 private static final UninitializedObject BLANK = new UninitializedObject();
4559
4560 private interface ArgumentReference {
4561
4562 Object snapshot();
4563
4564 Object currentValue();
4565
4566 void setValue(Object value);
4567
4568 void setScalarValue(Object value);
4569 }
4570
4571 private static final class IndirectArgumentReference implements ArgumentReference {
4572 private final Object[] frame;
4573 private final long offset;
4574 private final Object scalarValue;
4575
4576 private IndirectArgumentReference(Object[] frameParam, long offsetParam, Object scalarValueParam) {
4577 frame = frameParam;
4578 offset = offsetParam;
4579 scalarValue = scalarValueParam;
4580 }
4581
4582 @Override
4583 public Object snapshot() {
4584 return scalarValue;
4585 }
4586
4587 @Override
4588 public Object currentValue() {
4589 return frame[(int) offset];
4590 }
4591
4592 @Override
4593 public void setValue(Object value) {
4594 frame[(int) offset] = value;
4595 }
4596
4597 @Override
4598 public void setScalarValue(Object value) {
4599 if (isUntyped(currentValue())) {
4600 setValue(value);
4601 }
4602 }
4603 }
4604
4605 private static final class IndirectArrayArgumentReference implements ArgumentReference {
4606 private final Map<Object, Object> map;
4607 private final Object key;
4608 private Object detachedValue;
4609 private boolean detached;
4610
4611
4612 private int ownerFrame = -1;
4613
4614 private IndirectArrayArgumentReference(
4615 Map<Object, Object> mapParam,
4616 Object keyParam,
4617 Object scalarValueParam) {
4618 map = mapParam;
4619 key = keyParam;
4620 detachedValue = scalarValueParam;
4621 detached = !JRT.containsAwkKey(map, key);
4622 }
4623
4624 @Override
4625 public Object snapshot() {
4626 return detachedValue;
4627 }
4628
4629 @Override
4630 public Object currentValue() {
4631 return !detached && JRT.containsAwkKey(map, key) ?
4632 JRT.getAssocArrayValue(map, key) : detachedValue;
4633 }
4634
4635 @Override
4636 public void setValue(Object value) {
4637 if (!detached && JRT.containsAwkKey(map, key)) {
4638 map.put(key, value);
4639 } else {
4640 detachedValue = value;
4641 }
4642 }
4643
4644 @Override
4645 public void setScalarValue(Object value) {
4646 if (detached || !JRT.containsAwkKey(map, key)) {
4647 detachedValue = value;
4648 } else if (isUntyped(JRT.getAssocArrayValue(map, key))) {
4649 map.put(key, value);
4650 }
4651 }
4652
4653 private void detachIfMissing(Map<Object, Object> candidateMap) {
4654 if (!detached && map == candidateMap && !JRT.containsAwkKey(map, key)) {
4655 detached = true;
4656 }
4657 }
4658
4659 private boolean isAttached() {
4660 return !detached && JRT.containsAwkKey(map, key);
4661 }
4662
4663 private void detachIfReplaced(Object previousValue) {
4664 if (!detached
4665 && (!JRT.containsAwkKey(map, key)
4666 || JRT.getAssocArrayValue(map, key) != previousValue)) {
4667 detached = true;
4668 }
4669 }
4670 }
4671
4672
4673
4674
4675
4676 private static final Set<String> NON_PERSISTENT_GLOBALS = new HashSet<>(
4677 Arrays
4678 .asList(
4679 "ARGV",
4680 "ARGC",
4681 "ENVIRON",
4682 "RSTART",
4683 "RLENGTH",
4684 "IGNORECASE",
4685 "SYMTAB",
4686 "FUNCTAB"));
4687
4688 private static final class SingleRecordInputSource implements InputSource {
4689
4690 private final String record;
4691 private boolean consumed;
4692
4693 private SingleRecordInputSource(String record) {
4694 this.record = record;
4695 }
4696
4697 @Override
4698 public boolean nextRecord() {
4699 if (consumed || record == null) {
4700 return false;
4701 }
4702 consumed = true;
4703 return true;
4704 }
4705
4706 @Override
4707 public String getRecordText() {
4708 return consumed ? record : null;
4709 }
4710
4711 @Override
4712 public List<String> getFields() {
4713 return null;
4714 }
4715
4716 @Override
4717 public boolean isFromFilenameList() {
4718 return false;
4719 }
4720 }
4721
4722
4723
4724
4725 public static final int NULL_OFFSET = -1;
4726
4727 }