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.Arrays;
29 import java.util.HashSet;
30 import java.util.LinkedHashMap;
31 import java.util.LinkedHashSet;
32 import java.util.Objects;
33 import java.util.ArrayDeque;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Deque;
37 import java.util.Enumeration;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Locale;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.StringTokenizer;
44 import java.util.regex.Matcher;
45 import java.util.regex.Pattern;
46 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
47 import io.jawk.AwkExpression;
48 import io.jawk.AwkProgram;
49 import io.jawk.AwkSandboxException;
50 import io.jawk.ExitException;
51 import io.jawk.ext.AbstractExtension;
52 import io.jawk.ext.ExtensionFunction;
53 import io.jawk.ext.JawkExtension;
54 import io.jawk.intermediate.Address;
55 import io.jawk.intermediate.Opcode;
56 import io.jawk.intermediate.PositionTracker;
57 import io.jawk.intermediate.Tuple;
58 import io.jawk.intermediate.Tuple.BooleanTuple;
59 import io.jawk.intermediate.Tuple.CallFunctionTuple;
60 import io.jawk.intermediate.Tuple.ClassTuple;
61 import io.jawk.intermediate.Tuple.CountAndAppendTuple;
62 import io.jawk.intermediate.Tuple.CountTuple;
63 import io.jawk.intermediate.Tuple.DereferenceTuple;
64 import io.jawk.intermediate.Tuple.ExtensionTuple;
65 import io.jawk.intermediate.Tuple.InputFieldTuple;
66 import io.jawk.intermediate.Tuple.LongTuple;
67 import io.jawk.intermediate.Tuple.PushDoubleTuple;
68 import io.jawk.intermediate.Tuple.PushLongTuple;
69 import io.jawk.intermediate.Tuple.PushStringTuple;
70 import io.jawk.intermediate.Tuple.RegexTuple;
71 import io.jawk.intermediate.Tuple.SubstitutionVariableTuple;
72 import io.jawk.intermediate.Tuple.VariableTuple;
73 import io.jawk.intermediate.UninitializedObject;
74 import io.jawk.jrt.AssocArray;
75 import io.jawk.jrt.AwkRuntimeException;
76 import io.jawk.jrt.AwkSink;
77 import io.jawk.jrt.BlockManager;
78 import io.jawk.jrt.BlockObject;
79 import io.jawk.jrt.CharacterTokenizer;
80 import io.jawk.jrt.ConditionPair;
81 import io.jawk.jrt.InputSource;
82 import io.jawk.jrt.StreamInputSource;
83 import io.jawk.jrt.JRT;
84 import io.jawk.jrt.RegexTokenizer;
85 import io.jawk.jrt.SingleCharacterTokenizer;
86 import io.jawk.jrt.VariableManager;
87 import io.jawk.util.AwkSettings;
88 import io.jawk.jrt.BSDRandom;
89
90
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 public class AVM implements VariableManager, Closeable {
123
124 private RuntimeStack runtimeStack = new RuntimeStack();
125
126
127 private Deque<Object> operandStack = new ArrayDeque<Object>();
128 private List<String> arguments;
129 private boolean sortedArrayKeys;
130 private final Map<String, Object> baseInitialVariables;
131 private final Map<String, Object> baseSpecialVariables;
132 private Map<String, Object> executionInitialVariables;
133 private Map<String, Object> executionSpecialVariables;
134 private JRT jrt;
135 private Map<String, JawkExtension> extensionInstances;
136
137 private static final Object NULL_OPERAND = new Object();
138
139
140
141
142 private Object pop() {
143 Object value = operandStack.pop();
144 return value == NULL_OPERAND ? null : value;
145 }
146
147 private void push(Object o) {
148 operandStack.push(o == null ? NULL_OPERAND : o);
149 }
150
151 private final AwkSettings settings;
152 private final boolean profiling;
153 private final Map<Opcode, ProfilingReport.Accumulator> tupleProfilingStats;
154 private final Map<String, ProfilingReport.Accumulator> functionProfilingStats;
155 private final Deque<ActiveFunction> activeProfilingFunctions;
156 private boolean inputSourceFilelistAssignmentsApplied;
157 private InputSource resolvedInputSource;
158 private AwkExpression installedEvalExpression;
159 private boolean mergedGlobalLayoutActive;
160
161
162
163
164
165
166
167 public AVM() {
168 this(null, Collections.<String, JawkExtension>emptyMap());
169 }
170
171
172
173
174
175
176
177
178
179 public AVM(final AwkSettings parameters,
180 final Map<String, JawkExtension> extensionInstances) {
181 this(parameters, extensionInstances, false);
182 }
183
184
185
186
187
188
189
190
191
192 public AVM(
193 final AwkSettings parameters,
194 final Map<String, JawkExtension> extensionInstances,
195 final boolean profilingEnabled) {
196 this.settings = parameters != null ? parameters : AwkSettings.DEFAULT_SETTINGS;
197 this.extensionInstances = extensionInstances == null ?
198 Collections.<String, JawkExtension>emptyMap() : extensionInstances;
199 this.profiling = profilingEnabled;
200 if (profilingEnabled) {
201 this.tupleProfilingStats = new java.util.EnumMap<Opcode, ProfilingReport.Accumulator>(Opcode.class);
202 this.functionProfilingStats = new LinkedHashMap<String, ProfilingReport.Accumulator>();
203 this.activeProfilingFunctions = new ArrayDeque<ActiveFunction>();
204 } else {
205 this.tupleProfilingStats = null;
206 this.functionProfilingStats = null;
207 this.activeProfilingFunctions = null;
208 }
209
210 arguments = Collections.emptyList();
211 sortedArrayKeys = this.settings.isUseSortedArrayKeys();
212 baseInitialVariables = new HashMap<String, Object>(this.settings.getVariables());
213 baseSpecialVariables = JRT.copySpecialVariables(baseInitialVariables);
214 executionInitialVariables = baseInitialVariables;
215 executionSpecialVariables = baseSpecialVariables;
216
217 jrt = createJrt();
218 initExtensions();
219 }
220
221 protected JRT createJrt() {
222 return new JRT(this, this.settings.getLocale(), AwkSink.NOP_SINK, null);
223 }
224
225
226
227
228
229
230 protected AwkSettings getSettings() {
231 return settings;
232 }
233
234
235
236
237
238
239 @SuppressFBWarnings("EI_EXPOSE_REP")
240 public JRT getJrt() {
241 return jrt;
242 }
243
244
245
246
247
248
249
250 public void setAwkSink(AwkSink sink) {
251 jrt.setAwkSink(Objects.requireNonNull(sink, "sink"));
252 }
253
254
255
256
257
258
259
260 public void setErrorStream(PrintStream errorStream) {
261 jrt.setErrorStream(errorStream);
262 }
263
264
265
266
267
268
269 public AwkSink getAwkSink() {
270 return jrt.getAwkSink();
271 }
272
273
274
275
276
277
278 protected Locale getLocale() {
279 return jrt.getLocale();
280 }
281
282
283
284
285
286
287
288
289
290 public Object eval(AwkExpression expression) throws IOException {
291 AwkExpression compiledExpression = Objects.requireNonNull(expression, "expression");
292 installExpressionMetadata(compiledExpression);
293
294 try {
295 executeTuples(compiledExpression.top());
296 } catch (ExitException e) {
297
298
299 throwExitException = false;
300 exitCode = 0;
301 throw new IllegalStateException("eval(AwkExpression) cannot execute EXIT opcodes.", e);
302 }
303 return operandStack.isEmpty() ? null : pop();
304 }
305
306
307
308
309
310
311
312
313
314 public Object eval(AwkExpression expression, InputSource inputSource) throws IOException {
315 return eval(expression, inputSource, null);
316 }
317
318
319
320
321
322
323
324
325
326
327
328
329 public Object eval(
330 AwkExpression expression,
331 InputSource inputSource,
332 Map<String, Object> variableOverrides)
333 throws IOException {
334 prepareForEval(inputSource, Collections.<String>emptyList(), variableOverrides);
335 return eval(expression);
336 }
337
338
339
340
341
342
343
344
345
346 public void execute(AwkProgram program, InputSource inputSource) throws ExitException, IOException {
347 execute(program, inputSource, Collections.<String>emptyList(), null);
348 }
349
350
351
352
353
354
355
356
357
358
359 public void execute(AwkProgram program, InputSource inputSource, List<String> runtimeArguments)
360 throws ExitException,
361 IOException {
362 execute(program, inputSource, runtimeArguments, null);
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376
377 public void execute(
378 AwkProgram program,
379 InputSource inputSource,
380 List<String> runtimeArguments,
381 Map<String, Object> variableOverrides)
382 throws ExitException,
383 IOException {
384 AwkProgram compiledProgram = Objects.requireNonNull(program, "program");
385 InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
386 resetRuntimeState(runtimeArguments, variableOverrides);
387 installProgramMetadata(compiledProgram);
388
389 jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
390 if (!executionSpecialVariables.isEmpty()) {
391 jrt.applySpecialVariables(executionSpecialVariables);
392 }
393 rebindResolvedInputSource(resolvedSource);
394 executeTuples(compiledProgram.top());
395 }
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410 public void executePersistingGlobals(AwkProgram program, InputSource inputSource)
411 throws ExitException,
412 IOException {
413 executePersistingGlobals(program, inputSource, Collections.<String>emptyList(), null);
414 }
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430 public void executePersistingGlobals(
431 AwkProgram program,
432 InputSource inputSource,
433 List<String> runtimeArguments)
434 throws ExitException,
435 IOException {
436 executePersistingGlobals(program, inputSource, runtimeArguments, null);
437 }
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455 public void executePersistingGlobals(
456 AwkProgram program,
457 InputSource inputSource,
458 List<String> runtimeArguments,
459 Map<String, Object> variableOverrides)
460 throws ExitException,
461 IOException {
462 AwkProgram compiledProgram = Objects.requireNonNull(program, "program");
463 InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
464 mergeRuntimeState(runtimeArguments, variableOverrides, compiledProgram);
465
466 jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
467 if (!executionSpecialVariables.isEmpty()) {
468 jrt.applySpecialVariables(executionSpecialVariables);
469 }
470 rebindResolvedInputSource(resolvedSource);
471 executeTuples(compiledProgram.top());
472 }
473
474
475
476
477
478
479
480 public void clearPersistentGlobals() {
481 runtimeStack.clearGlobals();
482 mergedGlobalLayoutActive = false;
483 }
484
485
486
487
488
489
490
491
492
493
494 public Map<String, Object> snapshotPersistentMemory() {
495 return new LinkedHashMap<>(collectPersistentGlobalValues());
496 }
497
498
499
500
501
502
503
504
505
506
507
508 public void restorePersistentMemory(Map<String, Object> snapshot) {
509 Map<String, Object> restoredSnapshot = Objects.requireNonNull(snapshot, "snapshot");
510 Map<String, Object> restoredGlobals = filterToPersistentEligible(restoredSnapshot);
511 runtimeStack.clearGlobals();
512 if (!restoredGlobals.isEmpty()) {
513 runtimeStack.rebindGlobals(new ArrayList<>(restoredGlobals.keySet()));
514 applyGlobalsToStack(restoredGlobals);
515 }
516 mergedGlobalLayoutActive = false;
517 }
518
519 private void initExtensions() {
520 if (extensionInstances.isEmpty()) {
521 return;
522 }
523 Set<JawkExtension> initialized = new LinkedHashSet<JawkExtension>();
524 for (JawkExtension extension : extensionInstances.values()) {
525 if (initialized.add(extension)) {
526 extension.init(this, jrt, settings);
527 }
528 }
529 }
530
531
532
533 private long environOffset = NULL_OFFSET;
534 private long argcOffset = NULL_OFFSET;
535 private long argvOffset = NULL_OFFSET;
536
537 private static final Integer ZERO = Integer.valueOf(0);
538 private static final Integer ONE = Integer.valueOf(1);
539
540
541 private final BSDRandom randomNumberGenerator = new BSDRandom(1);
542
543
544
545
546
547
548
549
550
551 private int oldseed = 1;
552
553 private Address exitAddress = null;
554
555
556
557
558
559 private boolean withinEndBlocks = false;
560
561
562
563
564 private int exitCode = 0;
565
566
567
568
569 private boolean throwExitException = false;
570
571
572
573
574
575
576 private Map<String, Integer> globalVariableOffsets;
577
578
579
580
581 private Map<String, Boolean> globalVariableArrays;
582 private Set<String> functionNames = Collections.emptySet();
583 private Map<String, Integer> initializedEvalGlobalVariableOffsets;
584 private Map<String, Boolean> initializedEvalGlobalVariableArrays;
585
586
587
588
589
590
591
592
593
594
595 public boolean prepareForEval(String input) throws IOException {
596 return prepareForEval(new SingleRecordInputSource(input), Collections.<String>emptyList(), null);
597 }
598
599
600
601
602
603
604
605
606
607
608
609 public boolean prepareForEval(InputSource inputSource) throws IOException {
610 return prepareForEval(inputSource, Collections.<String>emptyList(), null);
611 }
612
613 private boolean prepareForEval(
614 InputSource inputSource,
615 List<String> runtimeArguments,
616 Map<String, Object> variableOverrides)
617 throws IOException {
618 InputSource resolvedSource = Objects.requireNonNull(inputSource, "inputSource");
619 resetRuntimeState(runtimeArguments, variableOverrides);
620 rebindResolvedInputSource(resolvedSource);
621
622 jrt.jrtCloseAll();
623 jrt.prepareForExecution(settings.getFieldSeparator(), settings.getDefaultRS());
624 if (!executionSpecialVariables.isEmpty()) {
625 jrt.applySpecialVariables(executionSpecialVariables);
626 }
627 return jrt.consumeInputForEval(resolvedInputSource);
628 }
629
630 private void resetRuntimeState(List<String> runtimeArguments, Map<String, Object> variableOverrides) {
631 resetTransientRuntimeState(runtimeArguments, variableOverrides);
632 runtimeStack.clearGlobals();
633 }
634
635 private void resetTransientRuntimeState(List<String> runtimeArguments, Map<String, Object> variableOverrides) {
636
637 operandStack.clear();
638 environOffset = NULL_OFFSET;
639 argcOffset = NULL_OFFSET;
640 argvOffset = NULL_OFFSET;
641 exitAddress = null;
642 withinEndBlocks = false;
643 exitCode = 0;
644 throwExitException = false;
645 inputSourceFilelistAssignmentsApplied = false;
646 globalVariableOffsets = null;
647 globalVariableArrays = null;
648 functionNames = Collections.emptySet();
649 initializedEvalGlobalVariableOffsets = null;
650 initializedEvalGlobalVariableArrays = null;
651 installedEvalExpression = null;
652 mergedGlobalLayoutActive = false;
653 runtimeStack.resetTransientState();
654 randomNumberGenerator.setSeed(1);
655 oldseed = 1;
656
657 prepareExecutionInputs(runtimeArguments, variableOverrides);
658 }
659
660 private void installExpressionMetadata(AwkExpression compiledExpression) {
661 if (installedEvalExpression == compiledExpression) {
662 return;
663 }
664 globalVariableOffsets = compiledExpression.getGlobalVariableOffsetMap();
665 globalVariableArrays = compiledExpression.getGlobalVariableAarrayMap();
666 functionNames = compiledExpression.getFunctionNameSet();
667 installedEvalExpression = compiledExpression;
668 }
669
670 private void installProgramMetadata(AwkProgram compiledProgram) {
671 globalVariableOffsets = compiledProgram.getGlobalVariableOffsetMap();
672 globalVariableArrays = compiledProgram.getGlobalVariableAarrayMap();
673 functionNames = compiledProgram.getFunctionNameSet();
674 }
675
676 private void rebindResolvedInputSource(InputSource resolvedSource) {
677 InputSource previousResolvedSource = resolvedInputSource;
678 if (previousResolvedSource != null && previousResolvedSource != resolvedSource) {
679 closeInputSource(previousResolvedSource);
680 }
681 resolvedInputSource = resolvedSource;
682 }
683
684 private boolean hasCompatibleEvalGlobalLayout(long numGlobals) {
685 Object[] globals = runtimeStack.getNumGlobals();
686 return globals != null
687 && globals.length == numGlobals
688 && Objects.equals(initializedEvalGlobalVariableOffsets, globalVariableOffsets)
689 && Objects.equals(initializedEvalGlobalVariableArrays, globalVariableArrays);
690 }
691
692
693
694
695
696
697
698
699
700
701 private void mergeRuntimeState(
702 List<String> runtimeArguments,
703 Map<String, Object> variableOverrides,
704 AwkProgram compiledProgram) {
705 Map<String, Object> carriedGlobals = collectPersistentGlobalValues();
706 resetTransientRuntimeState(runtimeArguments, variableOverrides);
707 installProgramMetadata(compiledProgram);
708
709 Map<String, Object> basePersistentSeeds = collectBasePersistentGlobalSeeds();
710 Map<String, Object> executionUserSeeds = collectExecutionUserGlobalSeeds(variableOverrides);
711 List<String> mergedGlobalNamesByOffset = buildMergedGlobalNamesByOffset(
712 carriedGlobals,
713 basePersistentSeeds,
714 executionUserSeeds);
715
716 runtimeStack.rebindGlobals(mergedGlobalNamesByOffset);
717 applyGlobalsToStack(carriedGlobals);
718 applyGlobalsToStack(basePersistentSeeds);
719 applyGlobalsToStack(executionUserSeeds);
720 mergedGlobalLayoutActive = true;
721 }
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736 private boolean hasCompatiblePersistentGlobalLayout(long numGlobals) {
737 Object[] globals = runtimeStack.getNumGlobals();
738 if (!mergedGlobalLayoutActive
739 || globals == null
740 || globalVariableOffsets == null
741 || globals.length < numGlobals) {
742 return false;
743 }
744 for (Map.Entry<String, Integer> entry : globalVariableOffsets.entrySet()) {
745 int offset = entry.getValue().intValue();
746 if (offset < 0 || offset >= globals.length || !entry.getKey().equals(runtimeStack.getGlobalName(offset))) {
747 return false;
748 }
749 }
750 return true;
751 }
752
753
754
755
756
757
758
759
760
761
762
763
764 private void applyExecutionInitialVariablesToGlobalSlots(boolean skipPersistentEligibleGlobals) {
765 for (Map.Entry<String, Object> entry : executionInitialVariables.entrySet()) {
766 String key = entry.getKey();
767 if (skipPersistentEligibleGlobals && isPersistentEligibleGlobal(key)) {
768 continue;
769 }
770 if (functionNames.contains(key)) {
771 throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + key + ").");
772 }
773 Integer offsetObj = globalVariableOffsets.get(key);
774 Boolean arrayObj = globalVariableArrays.get(key);
775 if (offsetObj != null) {
776 Object obj = normalizeVariableValue(entry.getValue());
777 if (arrayObj.booleanValue()) {
778 if (obj instanceof Map) {
779 runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
780 } else {
781 throw new IllegalArgumentException(
782 "Cannot assign a scalar to a non-scalar variable (" + key + ").");
783 }
784 } else {
785 runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
786 }
787 }
788 }
789 }
790
791
792
793
794
795
796
797
798
799
800 private void prepareExecutionInputs(
801 List<String> runtimeArguments,
802 Map<String, Object> variableOverrides) {
803 this.arguments = runtimeArguments != null ? new ArrayList<>(runtimeArguments) : Collections.<String>emptyList();
804
805 if (variableOverrides == null || variableOverrides.isEmpty()) {
806 executionInitialVariables = baseInitialVariables;
807 executionSpecialVariables = baseSpecialVariables;
808 } else {
809 executionInitialVariables = new HashMap<>(baseInitialVariables);
810 executionInitialVariables.putAll(variableOverrides);
811
812 Map<String, Object> specialOverrides = JRT.copySpecialVariables(variableOverrides);
813 if (specialOverrides.isEmpty()) {
814 executionSpecialVariables = baseSpecialVariables;
815 } else {
816 executionSpecialVariables = new HashMap<>(baseSpecialVariables);
817 executionSpecialVariables.putAll(specialOverrides);
818 }
819 }
820 }
821
822
823
824
825
826
827
828
829 private Map<String, Object> filterToPersistentEligible(Map<String, Object> source) {
830 Map<String, Object> result = new LinkedHashMap<>();
831 for (Map.Entry<String, Object> entry : source.entrySet()) {
832 if (isPersistentEligibleGlobal(entry.getKey())) {
833 result.put(entry.getKey(), entry.getValue());
834 }
835 }
836 return result;
837 }
838
839
840
841
842
843
844 private Map<String, Object> collectPersistentGlobalValues() {
845 return filterToPersistentEligible(runtimeStack.snapshotGlobalVariables());
846 }
847
848
849
850
851
852
853
854 private Map<String, Object> collectBasePersistentGlobalSeeds() {
855 Map<String, Object> basePersistentSeeds = new LinkedHashMap<>();
856 for (Map.Entry<String, Object> entry : baseInitialVariables.entrySet()) {
857 String name = entry.getKey();
858 if (isPersistentEligibleGlobal(name)) {
859 validateSeededGlobalName(name);
860 Object value = normalizeVariableValue(entry.getValue());
861 validateSeededGlobalValue(name, value);
862 basePersistentSeeds.put(name, value);
863 }
864 }
865 return basePersistentSeeds;
866 }
867
868
869
870
871
872
873
874
875
876
877
878 private Map<String, Object> collectExecutionUserGlobalSeeds(Map<String, Object> variableOverrides) {
879 Map<String, Object> executionUserSeeds = new LinkedHashMap<>();
880 if (variableOverrides != null) {
881 for (Map.Entry<String, Object> entry : variableOverrides.entrySet()) {
882 String name = entry.getKey();
883 if (isPersistentEligibleGlobal(name)) {
884 validateSeededGlobalName(name);
885 Object value = normalizeVariableValue(entry.getValue());
886 validateSeededGlobalValue(name, value);
887 executionUserSeeds.put(name, value);
888 }
889 }
890 }
891 return executionUserSeeds;
892 }
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907 private List<String> buildMergedGlobalNamesByOffset(
908 Map<String, Object> carriedGlobals,
909 Map<String, Object> basePersistentSeeds,
910 Map<String, Object> executionUserSeeds) {
911 LinkedHashSet<String> orderedNames = new LinkedHashSet<>();
912 List<Map.Entry<String, Integer>> compiledGlobals = new ArrayList<>(globalVariableOffsets.entrySet());
913 compiledGlobals.sort(java.util.Comparator.comparingInt(Map.Entry::getValue));
914 for (Map.Entry<String, Integer> entry : compiledGlobals) {
915 orderedNames.add(entry.getKey());
916 }
917 orderedNames.addAll(carriedGlobals.keySet());
918 orderedNames.addAll(basePersistentSeeds.keySet());
919 orderedNames.addAll(executionUserSeeds.keySet());
920 return new ArrayList<>(orderedNames);
921 }
922
923
924
925
926
927
928
929 private void applyGlobalsToStack(Map<String, Object> globals) {
930 for (Map.Entry<String, Object> entry : globals.entrySet()) {
931 runtimeStack.setGlobalVariable(entry.getKey(), entry.getValue());
932 }
933 }
934
935
936
937
938
939
940
941
942 private boolean isPersistentEligibleGlobal(String name) {
943 return name != null
944 && !JRT.isJrtManagedSpecialVariable(name)
945 && !NON_PERSISTENT_GLOBALS.contains(name);
946 }
947
948
949
950
951
952
953
954
955 private void validateSeededGlobalName(String name) {
956 if (functionNames.contains(name)) {
957 throw new IllegalArgumentException("Cannot assign a value to a function name (" + name + ").");
958 }
959 }
960
961
962
963
964
965
966
967
968 private void validateSeededGlobalValue(String name, Object value) {
969 Boolean arrayObj = globalVariableArrays.get(name);
970 if (Boolean.TRUE.equals(arrayObj) && !(value instanceof Map)) {
971 throw new IllegalArgumentException("Cannot assign a scalar to a non-scalar variable (" + name + ").");
972 }
973 }
974
975
976
977
978
979
980
981 private NameValueAssignment parseNameValueAssignment(String nameValue) {
982 int eqIdx = nameValue.indexOf('=');
983 if (eqIdx == 0) {
984 throw new IllegalArgumentException(
985 "Must have a non-blank variable name in a name=value variable assignment argument.");
986 }
987 String name = nameValue.substring(0, eqIdx);
988 String value = nameValue.substring(eqIdx + 1);
989 return new NameValueAssignment(name, coerceVariableAssignmentValue(value));
990 }
991
992
993
994
995
996
997
998
999 private Object coerceVariableAssignmentValue(String value) {
1000 try {
1001 return Integer.parseInt(value);
1002 } catch (NumberFormatException nfe) {
1003 try {
1004 return Double.parseDouble(value);
1005 } catch (NumberFormatException nfe2) {
1006 return value;
1007 }
1008 }
1009 }
1010
1011
1012
1013
1014
1015
1016
1017
1018 private void executeTuples(PositionTracker position)
1019 throws ExitException,
1020 IOException {
1021 Map<Integer, ConditionPair> conditionPairs = null;
1022 Opcode opcode = null;
1023 long tupleStartNanos = 0L;
1024 try {
1025 while (!position.isEOF()) {
1026
1027 Tuple tuple = position.current();
1028 opcode = tuple.getOpcode();
1029 if (profiling) {
1030 tupleStartNanos = beforeProfiledTuple(tuple, opcode);
1031 }
1032
1033 switch (opcode) {
1034 case PRINT: {
1035 execPrint((CountTuple) tuple);
1036 position.next();
1037 break;
1038 }
1039 case PRINT_TO_FILE: {
1040 execPrintToFile((CountAndAppendTuple) tuple);
1041 position.next();
1042 break;
1043 }
1044 case PRINT_TO_PIPE: {
1045 execPrintToPipe((CountTuple) tuple);
1046 position.next();
1047 break;
1048 }
1049 case PRINTF: {
1050 execPrintf((CountTuple) tuple);
1051 position.next();
1052 break;
1053 }
1054 case PRINTF_TO_FILE: {
1055 execPrintfToFile((CountAndAppendTuple) tuple);
1056 position.next();
1057 break;
1058 }
1059 case PRINTF_TO_PIPE: {
1060 execPrintfToPipe((CountTuple) tuple);
1061 position.next();
1062 break;
1063 }
1064 case SPRINTF: {
1065
1066
1067
1068
1069 CountTuple countTuple = (CountTuple) tuple;
1070 long numArgs = countTuple.getCount();
1071 push(sprintfFunction(numArgs));
1072 position.next();
1073 break;
1074 }
1075 case LENGTH: {
1076 execLength((CountTuple) tuple);
1077 position.next();
1078 break;
1079 }
1080 case PUSH_LONG: {
1081
1082 PushLongTuple pushTuple = (PushLongTuple) tuple;
1083 push(pushTuple.getValue());
1084 position.next();
1085 break;
1086 }
1087 case PUSH_DOUBLE: {
1088
1089 PushDoubleTuple pushTuple = (PushDoubleTuple) tuple;
1090 push(pushTuple.getValue());
1091 position.next();
1092 break;
1093 }
1094 case PUSH_STRING: {
1095
1096 PushStringTuple pushTuple = (PushStringTuple) tuple;
1097 push(pushTuple.getValue());
1098 position.next();
1099 break;
1100 }
1101 case POP: {
1102
1103 pop();
1104 position.next();
1105 break;
1106 }
1107 case IFFALSE: {
1108
1109
1110
1111
1112
1113
1114 boolean jump = !jrt.toBoolean(pop());
1115 if (jump) {
1116 position.jump(tuple.getAddress());
1117 } else {
1118 position.next();
1119 }
1120 break;
1121 }
1122 case TO_NUMBER: {
1123
1124
1125
1126
1127
1128 boolean val = jrt.toBoolean(pop());
1129 push(val ? ONE : ZERO);
1130 position.next();
1131 break;
1132 }
1133 case IFTRUE: {
1134
1135
1136
1137
1138
1139
1140 boolean jump = jrt.toBoolean(pop());
1141 if (jump) {
1142 position.jump(tuple.getAddress());
1143 } else {
1144 position.next();
1145 }
1146 break;
1147 }
1148 case NOT: {
1149
1150
1151 Object o = pop();
1152
1153 boolean result = jrt.toBoolean(o);
1154
1155 if (result) {
1156 push(0);
1157 } else {
1158 push(1);
1159 }
1160 position.next();
1161 break;
1162 }
1163 case NEGATE: {
1164
1165
1166 double d = JRT.toDouble(pop());
1167 if (JRT.isActuallyLong(d)) {
1168 push((long) -Math.rint(d));
1169 } else {
1170 push(-d);
1171 }
1172 position.next();
1173 break;
1174 }
1175 case UNARY_PLUS: {
1176
1177 double d = JRT.toDouble(pop());
1178 if (JRT.isActuallyLong(d)) {
1179 push((long) Math.rint(d));
1180 } else {
1181 push(d);
1182 }
1183 position.next();
1184 break;
1185 }
1186 case GOTO: {
1187
1188
1189 position.jump(tuple.getAddress());
1190 break;
1191 }
1192 case NOP: {
1193
1194 position.next();
1195 break;
1196 }
1197 case CONCAT: {
1198
1199
1200 String s2 = jrt.toAwkString(pop());
1201 String s1 = jrt.toAwkString(pop());
1202 String resultString = s1 + s2;
1203 push(resultString);
1204 position.next();
1205 break;
1206 }
1207 case ASSIGN: {
1208
1209
1210
1211 VariableTuple variableTuple = (VariableTuple) tuple;
1212 Object value = pop();
1213 assign(variableTuple.getVariableOffset(), value, variableTuple.isGlobal(), position);
1214 position.next();
1215 break;
1216 }
1217 case ASSIGN_ARRAY: {
1218
1219
1220
1221
1222 Object arrIdx = pop();
1223 Object rhs = pop();
1224 if (rhs == null) {
1225 rhs = BLANK;
1226 }
1227 VariableTuple variableTuple = (VariableTuple) tuple;
1228 long offset = variableTuple.getVariableOffset();
1229 boolean isGlobal = variableTuple.isGlobal();
1230 assignArray(offset, arrIdx, rhs, isGlobal);
1231 position.next();
1232 break;
1233 }
1234 case ASSIGN_MAP_ELEMENT: {
1235
1236
1237
1238 Object arrIdx = pop();
1239 Map<Object, Object> array = toMap(pop());
1240 Object rhs = pop();
1241 if (rhs == null) {
1242 rhs = BLANK;
1243 }
1244 assignMapElement(array, arrIdx, rhs);
1245 position.next();
1246 break;
1247 }
1248 case PLUS_EQ_ARRAY:
1249 case MINUS_EQ_ARRAY:
1250 case MULT_EQ_ARRAY:
1251 case DIV_EQ_ARRAY:
1252 case MOD_EQ_ARRAY:
1253 case POW_EQ_ARRAY: {
1254
1255
1256
1257
1258 Object arrIdx = pop();
1259 Object rhs = pop();
1260 if (rhs == null) {
1261 rhs = BLANK;
1262 }
1263 VariableTuple variableTuple = (VariableTuple) tuple;
1264 long offset = variableTuple.getVariableOffset();
1265 boolean isGlobal = variableTuple.isGlobal();
1266
1267 double val = JRT.toDouble(rhs);
1268
1269 Map<Object, Object> array = ensureMapVariable(offset, isGlobal);
1270 checkScalar(arrIdx);
1271 Object o = array.get(arrIdx);
1272 double origVal = JRT.toDouble(o);
1273
1274 double newVal;
1275
1276 switch (opcode) {
1277 case PLUS_EQ_ARRAY:
1278 newVal = origVal + val;
1279 break;
1280 case MINUS_EQ_ARRAY:
1281 newVal = origVal - val;
1282 break;
1283 case MULT_EQ_ARRAY:
1284 newVal = origVal * val;
1285 break;
1286 case DIV_EQ_ARRAY:
1287 newVal = origVal / val;
1288 break;
1289 case MOD_EQ_ARRAY:
1290 newVal = origVal % val;
1291 break;
1292 case POW_EQ_ARRAY:
1293 newVal = Math.pow(origVal, val);
1294 break;
1295 default:
1296 throw new Error("Invalid op code here: " + opcode);
1297 }
1298
1299 if (JRT.isActuallyLong(newVal)) {
1300 assignArray(offset, arrIdx, (long) Math.rint(newVal), isGlobal);
1301 } else {
1302 assignArray(offset, arrIdx, newVal, isGlobal);
1303 }
1304 position.next();
1305 break;
1306 }
1307 case PLUS_EQ_MAP_ELEMENT:
1308 case MINUS_EQ_MAP_ELEMENT:
1309 case MULT_EQ_MAP_ELEMENT:
1310 case DIV_EQ_MAP_ELEMENT:
1311 case MOD_EQ_MAP_ELEMENT:
1312 case POW_EQ_MAP_ELEMENT: {
1313
1314
1315
1316 Object arrIdx = pop();
1317 Map<Object, Object> array = toMap(pop());
1318 Object rhs = pop();
1319 if (rhs == null) {
1320 rhs = BLANK;
1321 }
1322
1323 double val = JRT.toDouble(rhs);
1324 checkScalar(arrIdx);
1325 Object o = array.get(arrIdx);
1326 double origVal = JRT.toDouble(o);
1327 double newVal;
1328
1329 switch (opcode) {
1330 case PLUS_EQ_MAP_ELEMENT:
1331 newVal = origVal + val;
1332 break;
1333 case MINUS_EQ_MAP_ELEMENT:
1334 newVal = origVal - val;
1335 break;
1336 case MULT_EQ_MAP_ELEMENT:
1337 newVal = origVal * val;
1338 break;
1339 case DIV_EQ_MAP_ELEMENT:
1340 newVal = origVal / val;
1341 break;
1342 case MOD_EQ_MAP_ELEMENT:
1343 newVal = origVal % val;
1344 break;
1345 case POW_EQ_MAP_ELEMENT:
1346 newVal = Math.pow(origVal, val);
1347 break;
1348 default:
1349 throw new Error("Invalid op code here: " + opcode);
1350 }
1351
1352 if (JRT.isActuallyLong(newVal)) {
1353 assignMapElement(array, arrIdx, (long) Math.rint(newVal));
1354 } else {
1355 assignMapElement(array, arrIdx, newVal);
1356 }
1357 position.next();
1358 break;
1359 }
1360
1361 case ASSIGN_AS_INPUT: {
1362
1363 jrt.assignInputLineFromGetline(pop());
1364 push(jrt.getInputLine());
1365 position.next();
1366 break;
1367 }
1368
1369 case ASSIGN_AS_INPUT_FIELD: {
1370
1371
1372 Object fieldNumObj = pop();
1373 long fieldNum = JRT.parseFieldNumber(fieldNumObj);
1374 String value = pop().toString();
1375 push(value);
1376 if (fieldNum == 0) {
1377 jrt.setInputLine(value);
1378 jrt.jrtParseFields();
1379 } else {
1380 jrt.jrtSetInputField(value, fieldNum);
1381 }
1382 position.next();
1383 break;
1384 }
1385 case PLUS_EQ:
1386 case MINUS_EQ:
1387 case MULT_EQ:
1388 case DIV_EQ:
1389 case MOD_EQ:
1390 case POW_EQ: {
1391
1392
1393
1394 VariableTuple variableTuple = (VariableTuple) tuple;
1395 long offset = variableTuple.getVariableOffset();
1396 boolean isGlobal = variableTuple.isGlobal();
1397 Object o1 = runtimeStack.getVariable(offset, isGlobal);
1398 if (o1 == null) {
1399 o1 = BLANK;
1400 }
1401 Object o2 = pop();
1402 double d1 = JRT.toDouble(o1);
1403 double d2 = JRT.toDouble(o2);
1404 double ans;
1405 switch (opcode) {
1406 case PLUS_EQ:
1407 ans = d1 + d2;
1408 break;
1409 case MINUS_EQ:
1410 ans = d1 - d2;
1411 break;
1412 case MULT_EQ:
1413 ans = d1 * d2;
1414 break;
1415 case DIV_EQ:
1416 ans = d1 / d2;
1417 break;
1418 case MOD_EQ:
1419 ans = d1 % d2;
1420 break;
1421 case POW_EQ:
1422 ans = Math.pow(d1, d2);
1423 break;
1424 default:
1425 throw new Error("Invalid opcode here: " + opcode);
1426 }
1427 if (JRT.isActuallyLong(ans)) {
1428 long integral = (long) Math.rint(ans);
1429 push(integral);
1430 runtimeStack.setVariable(offset, integral, isGlobal);
1431 } else {
1432 push(ans);
1433 runtimeStack.setVariable(offset, ans, isGlobal);
1434 }
1435 position.next();
1436 break;
1437 }
1438 case PLUS_EQ_INPUT_FIELD:
1439 case MINUS_EQ_INPUT_FIELD:
1440 case MULT_EQ_INPUT_FIELD:
1441 case DIV_EQ_INPUT_FIELD:
1442 case MOD_EQ_INPUT_FIELD:
1443 case POW_EQ_INPUT_FIELD: {
1444
1445
1446
1447
1448 long fieldnum = JRT.parseFieldNumber(pop());
1449 double incval = JRT.toDouble(pop());
1450
1451
1452 Object numObj = jrt.jrtGetInputField(fieldnum);
1453 double num;
1454 switch (opcode) {
1455 case PLUS_EQ_INPUT_FIELD:
1456 num = JRT.toDouble(numObj) + incval;
1457 break;
1458 case MINUS_EQ_INPUT_FIELD:
1459 num = JRT.toDouble(numObj) - incval;
1460 break;
1461 case MULT_EQ_INPUT_FIELD:
1462 num = JRT.toDouble(numObj) * incval;
1463 break;
1464 case DIV_EQ_INPUT_FIELD:
1465 num = JRT.toDouble(numObj) / incval;
1466 break;
1467 case MOD_EQ_INPUT_FIELD:
1468 num = JRT.toDouble(numObj) % incval;
1469 break;
1470 case POW_EQ_INPUT_FIELD:
1471 num = Math.pow(JRT.toDouble(numObj), incval);
1472 break;
1473 default:
1474 throw new Error("Invalid opcode here: " + opcode);
1475 }
1476 setNumOnJRT(fieldnum, num);
1477
1478
1479 push(num);
1480 position.next();
1481
1482 break;
1483 }
1484 case INC: {
1485
1486
1487 VariableTuple variableTuple = (VariableTuple) tuple;
1488 inc(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1489 position.next();
1490 break;
1491 }
1492 case DEC: {
1493
1494
1495 VariableTuple variableTuple = (VariableTuple) tuple;
1496 dec(variableTuple.getVariableOffset(), variableTuple.isGlobal());
1497 position.next();
1498 break;
1499 }
1500 case POSTINC: {
1501
1502
1503 pop();
1504 VariableTuple variableTuple = (VariableTuple) tuple;
1505 push(inc(variableTuple.getVariableOffset(), variableTuple.isGlobal()));
1506 position.next();
1507 break;
1508 }
1509 case POSTDEC: {
1510
1511
1512 pop();
1513 VariableTuple variableTuple = (VariableTuple) tuple;
1514 push(dec(variableTuple.getVariableOffset(), variableTuple.isGlobal()));
1515 position.next();
1516 break;
1517 }
1518 case INC_ARRAY_REF: {
1519
1520
1521
1522 VariableTuple variableTuple = (VariableTuple) tuple;
1523 boolean isGlobal = variableTuple.isGlobal();
1524 Map<Object, Object> aa = ensureMapVariable(variableTuple.getVariableOffset(), isGlobal);
1525 Object key = pop();
1526 checkScalar(key);
1527 Object o = aa.get(key);
1528 double ans = JRT.toDouble(o) + 1;
1529 if (JRT.isActuallyLong(ans)) {
1530 aa.put(key, (long) Math.rint(ans));
1531 } else {
1532 aa.put(key, ans);
1533 }
1534 position.next();
1535 break;
1536 }
1537 case DEC_ARRAY_REF: {
1538
1539
1540
1541 VariableTuple variableTuple = (VariableTuple) tuple;
1542 boolean isGlobal = variableTuple.isGlobal();
1543 Map<Object, Object> aa = ensureMapVariable(variableTuple.getVariableOffset(), isGlobal);
1544 Object key = pop();
1545 checkScalar(key);
1546 Object o = aa.get(key);
1547 double ans = JRT.toDouble(o) - 1;
1548 if (JRT.isActuallyLong(ans)) {
1549 aa.put(key, (long) Math.rint(ans));
1550 } else {
1551 aa.put(key, ans);
1552 }
1553 position.next();
1554 break;
1555 }
1556 case INC_MAP_REF: {
1557
1558
1559 Object key = pop();
1560 checkScalar(key);
1561 Map<Object, Object> aa = toMap(pop());
1562 Object o = aa.get(key);
1563 double ans = JRT.toDouble(o) + 1;
1564 if (JRT.isActuallyLong(ans)) {
1565 aa.put(key, (long) Math.rint(ans));
1566 } else {
1567 aa.put(key, ans);
1568 }
1569 position.next();
1570 break;
1571 }
1572 case DEC_MAP_REF: {
1573
1574
1575 Object key = pop();
1576 checkScalar(key);
1577 Map<Object, Object> aa = toMap(pop());
1578 Object o = aa.get(key);
1579 double ans = JRT.toDouble(o) - 1;
1580 if (JRT.isActuallyLong(ans)) {
1581 aa.put(key, (long) Math.rint(ans));
1582 } else {
1583 aa.put(key, ans);
1584 }
1585 position.next();
1586 break;
1587 }
1588 case INC_DOLLAR_REF: {
1589
1590 long fieldnum = JRT.parseFieldNumber(pop());
1591
1592 Object numObj = jrt.jrtGetInputField(fieldnum);
1593 double original = JRT.toDouble(numObj);
1594 double num = original + 1;
1595 setNumOnJRT(fieldnum, num);
1596
1597 if (JRT.isActuallyLong(original)) {
1598 push((long) Math.rint(original));
1599 } else {
1600 push(Double.valueOf(original));
1601 }
1602
1603 position.next();
1604 break;
1605 }
1606 case DEC_DOLLAR_REF: {
1607
1608
1609 long fieldnum = JRT.parseFieldNumber(pop());
1610
1611 Object numObj = jrt.jrtGetInputField(fieldnum);
1612 double original = JRT.toDouble(numObj);
1613 double num = original - 1;
1614 setNumOnJRT(fieldnum, num);
1615
1616 if (JRT.isActuallyLong(original)) {
1617 push((long) Math.rint(original));
1618 } else {
1619 push(Double.valueOf(original));
1620 }
1621
1622 position.next();
1623 break;
1624 }
1625 case DEREFERENCE: {
1626
1627
1628 DereferenceTuple dereferenceTuple = (DereferenceTuple) tuple;
1629 boolean isGlobal = dereferenceTuple.isGlobal();
1630 long offset = dereferenceTuple.getVariableOffset();
1631 Object o = runtimeStack.getVariable(offset, isGlobal);
1632 if (o == null) {
1633 if (dereferenceTuple.isArray()) {
1634
1635 push(runtimeStack.setVariable(offset, newAwkArray(), isGlobal));
1636 } else {
1637 push(runtimeStack.setVariable(offset, BLANK, isGlobal));
1638 }
1639 } else {
1640 push(o);
1641 }
1642 position.next();
1643 break;
1644 }
1645 case DEREF_ARRAY: {
1646
1647 Object idx = pop();
1648 checkScalar(idx);
1649 Map<Object, Object> map = toMap(pop());
1650 Object o = JRT.getAwkValue(map, idx);
1651 push(o);
1652 position.next();
1653 break;
1654 }
1655 case ENSURE_ARRAY_ELEMENT: {
1656
1657
1658 Object idx = pop();
1659 Map<Object, Object> map = toMap(pop());
1660 push(ensureArrayInArray(map, idx));
1661 position.next();
1662 break;
1663 }
1664 case PEEK_ARRAY_ELEMENT: {
1665
1666 Object idx = pop();
1667 checkScalar(idx);
1668 Map<Object, Object> map = toMap(pop());
1669 if (map instanceof AssocArray && !JRT.containsAwkKey(map, idx)) {
1670 push(BLANK);
1671 } else {
1672 Object value = map.get(idx);
1673 push(value != null ? value : BLANK);
1674 }
1675 position.next();
1676 break;
1677 }
1678 case SRAND: {
1679
1680
1681 CountTuple countTuple = (CountTuple) tuple;
1682 long numArgs = countTuple.getCount();
1683 int seed;
1684 if (numArgs == 0) {
1685
1686 seed = JRT.timeSeed();
1687 } else {
1688 Object o = pop();
1689 if (o instanceof Double) {
1690 seed = ((Double) o).intValue();
1691 } else if (o instanceof Long) {
1692 seed = ((Long) o).intValue();
1693 } else if (o instanceof Integer) {
1694 seed = ((Integer) o).intValue();
1695 } else {
1696 try {
1697 seed = Integer.parseInt(o.toString());
1698 } catch (NumberFormatException nfe) {
1699 seed = 0;
1700 }
1701 }
1702 }
1703 randomNumberGenerator.setSeed(seed);
1704 push(oldseed);
1705 oldseed = seed;
1706 position.next();
1707 break;
1708 }
1709 case RAND: {
1710 push(randomNumberGenerator.nextDouble());
1711 position.next();
1712 break;
1713 }
1714 case INTFUNC: {
1715
1716 push((long) JRT.toDouble(pop()));
1717 position.next();
1718 break;
1719 }
1720 case SQRT: {
1721
1722 push(Math.sqrt(JRT.toDouble(pop())));
1723 position.next();
1724 break;
1725 }
1726 case LOG: {
1727
1728 push(Math.log(JRT.toDouble(pop())));
1729 position.next();
1730 break;
1731 }
1732 case EXP: {
1733
1734 push(Math.exp(JRT.toDouble(pop())));
1735 position.next();
1736 break;
1737 }
1738 case SIN: {
1739
1740 push(Math.sin(JRT.toDouble(pop())));
1741 position.next();
1742 break;
1743 }
1744 case COS: {
1745
1746 push(Math.cos(JRT.toDouble(pop())));
1747 position.next();
1748 break;
1749 }
1750 case ATAN2: {
1751
1752
1753 double d2 = JRT.toDouble(pop());
1754 double d1 = JRT.toDouble(pop());
1755 push(Math.atan2(d1, d2));
1756 position.next();
1757 break;
1758 }
1759 case MATCH: {
1760 execMatch();
1761 position.next();
1762 break;
1763 }
1764 case INDEX: {
1765
1766
1767 String s2 = jrt.toAwkString(pop());
1768 String s1 = jrt.toAwkString(pop());
1769 push(s1.indexOf(s2) + 1);
1770 position.next();
1771 break;
1772 }
1773 case SUB_FOR_DOLLAR_0: {
1774 execSubForDollar0((BooleanTuple) tuple);
1775 position.next();
1776 break;
1777 }
1778 case SUB_FOR_DOLLAR_REFERENCE: {
1779 execSubForDollarReference((BooleanTuple) tuple);
1780 position.next();
1781 break;
1782 }
1783 case SUB_FOR_VARIABLE: {
1784 execSubForVariable((SubstitutionVariableTuple) tuple, position);
1785 position.next();
1786 break;
1787 }
1788 case SUB_FOR_ARRAY_REFERENCE: {
1789 execSubForArrayReference((SubstitutionVariableTuple) tuple);
1790 position.next();
1791 break;
1792 }
1793 case SUB_FOR_MAP_REFERENCE: {
1794 execSubForMapReference((BooleanTuple) tuple);
1795 position.next();
1796 break;
1797 }
1798 case SPLIT: {
1799 execSplit((CountTuple) tuple, position);
1800 position.next();
1801 break;
1802 }
1803 case SUBSTR: {
1804 execSubstr((CountTuple) tuple);
1805 position.next();
1806 break;
1807 }
1808 case TOLOWER: {
1809
1810 push(jrt.toAwkString(pop()).toLowerCase());
1811 position.next();
1812 break;
1813 }
1814 case TOUPPER: {
1815
1816 push(jrt.toAwkString(pop()).toUpperCase());
1817 position.next();
1818 break;
1819 }
1820 case SYSTEM: {
1821
1822 String s = jrt.toAwkString(pop());
1823 push(jrt.jrtSystem(s));
1824 position.next();
1825 break;
1826 }
1827 case SWAP: {
1828
1829
1830 Object o1 = pop();
1831 Object o2 = pop();
1832 push(o1);
1833 push(o2);
1834 position.next();
1835 break;
1836 }
1837 case CMP_EQ: {
1838
1839
1840 Object o2 = pop();
1841 Object o1 = pop();
1842 push(JRT.compare2(o1, o2, 0) ? ONE : ZERO);
1843 position.next();
1844 break;
1845 }
1846 case CMP_LT: {
1847
1848
1849 Object o2 = pop();
1850 Object o1 = pop();
1851 push(JRT.compare2(o1, o2, -1) ? ONE : ZERO);
1852 position.next();
1853 break;
1854 }
1855 case CMP_GT: {
1856
1857
1858 Object o2 = pop();
1859 Object o1 = pop();
1860 push(JRT.compare2(o1, o2, 1) ? ONE : ZERO);
1861 position.next();
1862 break;
1863 }
1864 case MATCHES: {
1865
1866
1867 Object o2 = pop();
1868 Object o1 = pop();
1869
1870 String s = o1.toString();
1871
1872 if (o2 instanceof Pattern) {
1873 Pattern p = (Pattern) o2;
1874 Matcher m = p.matcher(s);
1875
1876
1877 boolean result = m.find();
1878 push(result ? 1 : 0);
1879 } else {
1880 String r = jrt.toAwkString(o2);
1881 boolean result = Pattern.compile(r).matcher(s).find();
1882 push(result ? 1 : 0);
1883 }
1884 position.next();
1885 break;
1886 }
1887 case ADD: {
1888
1889
1890 Object o2 = pop();
1891 Object o1 = pop();
1892 double d1 = JRT.toDouble(o1);
1893 double d2 = JRT.toDouble(o2);
1894 double ans = d1 + d2;
1895 if (JRT.isActuallyLong(ans)) {
1896 push((long) Math.rint(ans));
1897 } else {
1898 push(ans);
1899 }
1900 position.next();
1901 break;
1902 }
1903 case SUBTRACT: {
1904
1905
1906 Object o2 = pop();
1907 Object o1 = pop();
1908 double d1 = JRT.toDouble(o1);
1909 double d2 = JRT.toDouble(o2);
1910 double ans = d1 - d2;
1911 if (JRT.isActuallyLong(ans)) {
1912 push((long) Math.rint(ans));
1913 } else {
1914 push(ans);
1915 }
1916 position.next();
1917 break;
1918 }
1919 case MULTIPLY: {
1920
1921
1922 Object o2 = pop();
1923 Object o1 = pop();
1924 double d1 = JRT.toDouble(o1);
1925 double d2 = JRT.toDouble(o2);
1926 double ans = d1 * d2;
1927 if (JRT.isActuallyLong(ans)) {
1928 push((long) Math.rint(ans));
1929 } else {
1930 push(ans);
1931 }
1932 position.next();
1933 break;
1934 }
1935 case DIVIDE: {
1936
1937
1938 Object o2 = pop();
1939 Object o1 = pop();
1940 double d1 = JRT.toDouble(o1);
1941 double d2 = JRT.toDouble(o2);
1942 double ans = d1 / d2;
1943 if (JRT.isActuallyLong(ans)) {
1944 push((long) Math.rint(ans));
1945 } else {
1946 push(ans);
1947 }
1948 position.next();
1949 break;
1950 }
1951 case MOD: {
1952
1953
1954 Object o2 = pop();
1955 Object o1 = pop();
1956 double d1 = JRT.toDouble(o1);
1957 double d2 = JRT.toDouble(o2);
1958 double ans = d1 % d2;
1959 if (JRT.isActuallyLong(ans)) {
1960 push((long) Math.rint(ans));
1961 } else {
1962 push(ans);
1963 }
1964 position.next();
1965 break;
1966 }
1967 case POW: {
1968
1969
1970 Object o2 = pop();
1971 Object o1 = pop();
1972 double d1 = JRT.toDouble(o1);
1973 double d2 = JRT.toDouble(o2);
1974 double ans = Math.pow(d1, d2);
1975 if (JRT.isActuallyLong(ans)) {
1976 push((long) Math.rint(ans));
1977 } else {
1978 push(ans);
1979 }
1980 position.next();
1981 break;
1982 }
1983 case DUP: {
1984
1985 Object o = pop();
1986 push(o);
1987 push(o);
1988 position.next();
1989 break;
1990 }
1991 case KEYLIST: {
1992 Object o = pop();
1993 if (o == null || o instanceof UninitializedObject) {
1994 push(new ArrayDeque<>());
1995 position.next();
1996 break;
1997 }
1998 if (!(o instanceof Map)) {
1999 throw new AwkRuntimeException(
2000 position.lineNumber(),
2001 "Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
2002 }
2003 @SuppressWarnings("unchecked")
2004 Map<Object, Object> map = (Map<Object, Object>) o;
2005 push(new ArrayDeque<>(map.keySet()));
2006 position.next();
2007 break;
2008 }
2009 case IS_EMPTY_KEYLIST: {
2010
2011
2012 Object o = pop();
2013 if (o == null || !(o instanceof Deque)) {
2014 throw new AwkRuntimeException(
2015 position.lineNumber(),
2016 "Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
2017 }
2018 Deque<?> keylist = (Deque<?>) o;
2019 if (keylist.isEmpty()) {
2020 position.jump(tuple.getAddress());
2021 } else {
2022 position.next();
2023 }
2024 break;
2025 }
2026 case GET_FIRST_AND_REMOVE_FROM_KEYLIST: {
2027
2028 Object o = pop();
2029 if (o == null || !(o instanceof Deque)) {
2030 throw new AwkRuntimeException(
2031 position.lineNumber(),
2032 "Cannot get a key list (via 'in') of a non associative array. arg = " + o.getClass() + ", " + o);
2033 }
2034
2035 Deque<?> keylist = (Deque<?>) o;
2036 push(keylist.removeFirst());
2037 position.next();
2038 break;
2039 }
2040 case CHECK_CLASS: {
2041
2042
2043 ClassTuple checkTuple = (ClassTuple) tuple;
2044 Object o = pop();
2045 if (!checkTuple.getType().isInstance(o)) {
2046 throw new AwkRuntimeException(
2047 position.lineNumber(),
2048 "Verification failed. Top-of-stack = " + o.getClass() + " isn't an instance of "
2049 + checkTuple.getType());
2050 }
2051 push(o);
2052 position.next();
2053 break;
2054 }
2055 case CONSUME_INPUT: {
2056
2057
2058 applyInputSourceFilelistAssignmentsIfNeeded();
2059 if (jrt.consumeInput(resolvedInputSource)) {
2060 position.next();
2061 } else {
2062 position.jump(tuple.getAddress());
2063 }
2064 break;
2065 }
2066
2067 case GETLINE_INPUT: {
2068 applyInputSourceFilelistAssignmentsIfNeeded();
2069 push(jrt.consumeInput(resolvedInputSource) ? 1 : 0);
2070 position.next();
2071 break;
2072 }
2073 case GETLINE_INPUT_TO_TARGET: {
2074 applyInputSourceFilelistAssignmentsIfNeeded();
2075 String input = jrt.consumeInputToTarget(resolvedInputSource);
2076 if (input != null) {
2077 push(1);
2078 push(input);
2079 } else {
2080 push(0);
2081 push("");
2082 }
2083 position.next();
2084 break;
2085 }
2086 case USE_AS_FILE_INPUT: {
2087
2088 String s = jrt.toAwkString(pop());
2089 if (jrt.jrtConsumeFileInput(s)) {
2090 push(1);
2091 push(jrt.getInputLine());
2092 } else {
2093 push(0);
2094 push("");
2095 }
2096 position.next();
2097 break;
2098 }
2099 case USE_AS_COMMAND_INPUT: {
2100
2101 String s = jrt.toAwkString(pop());
2102 if (jrt.jrtConsumeCommandInput(s)) {
2103 push(1);
2104 push(jrt.getInputLine());
2105 } else {
2106 push(0);
2107 push("");
2108 }
2109 position.next();
2110 break;
2111 }
2112 case ENVIRON_OFFSET: {
2113
2114
2115 LongTuple offsetTuple = (LongTuple) tuple;
2116 environOffset = offsetTuple.getValue();
2117
2118 Map<String, String> env = System.getenv();
2119 for (Map.Entry<String, String> var : env.entrySet()) {
2120 assignArray(environOffset, var.getKey(), var.getValue(), true);
2121 pop();
2122 }
2123 position.next();
2124 break;
2125 }
2126 case ARGC_OFFSET: {
2127
2128 LongTuple offsetTuple = (LongTuple) tuple;
2129 argcOffset = offsetTuple.getValue();
2130
2131
2132 assign(argcOffset, arguments.size() + 1, true, position);
2133 pop();
2134 position.next();
2135 break;
2136 }
2137 case ARGV_OFFSET: {
2138
2139 LongTuple offsetTuple = (LongTuple) tuple;
2140 argvOffset = offsetTuple.getValue();
2141
2142 int argc = (int) JRT.toDouble(runtimeStack.getVariable(argcOffset, true));
2143 assignArray(argvOffset, 0, "jawk", true);
2144 pop();
2145 for (int i = 1; i < argc; i++) {
2146
2147 assignArray(argvOffset, i, arguments.get(i - 1), true);
2148 pop();
2149 }
2150 position.next();
2151 break;
2152 }
2153 case GET_INPUT_FIELD: {
2154
2155 Object fieldNumber = pop();
2156 push(jrt.jrtGetInputField(fieldNumber));
2157 position.next();
2158 break;
2159 }
2160 case GET_INPUT_FIELD_CONST: {
2161 InputFieldTuple inputFieldTuple = (InputFieldTuple) tuple;
2162 long fieldnum = inputFieldTuple.getFieldIndex();
2163 push(jrt.jrtGetInputField(fieldnum));
2164 position.next();
2165 break;
2166 }
2167 case APPLY_RS: {
2168 jrt.applyRS(jrt.getRSVar());
2169 position.next();
2170 break;
2171 }
2172 case CALL_FUNCTION: {
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182 CallFunctionTuple callTuple = (CallFunctionTuple) tuple;
2183 Address funcAddr = callTuple.getAddress();
2184 long numFormalParams = callTuple.getNumFormalParams();
2185 long numActualParams = callTuple.getNumActualParams();
2186 runtimeStack.pushFrame(numFormalParams, position.currentIndex());
2187
2188 for (long i = numActualParams - 1; i >= 0; i--) {
2189 runtimeStack.setVariable(i, pop(), false);
2190 }
2191 position.jump(funcAddr);
2192
2193 break;
2194 }
2195 case FUNCTION: {
2196
2197
2198
2199
2200 position.next();
2201 break;
2202 }
2203 case SET_RETURN_RESULT: {
2204
2205 runtimeStack.setReturnValue(pop());
2206 position.next();
2207 break;
2208 }
2209 case RETURN_FROM_FUNCTION: {
2210 position.jump(runtimeStack.popFrame());
2211 push(runtimeStack.getReturnValue());
2212 position.next();
2213 break;
2214 }
2215 case SET_NUM_GLOBALS: {
2216 execSetNumGlobals((CountTuple) tuple);
2217 position.next();
2218 break;
2219 }
2220 case CLOSE: {
2221
2222 String s = jrt.toAwkString(pop());
2223 push(jrt.jrtClose(s));
2224 position.next();
2225 break;
2226 }
2227 case APPLY_SUBSEP: {
2228 execApplySubsep((CountTuple) tuple);
2229 position.next();
2230 break;
2231 }
2232 case DELETE_ARRAY_ELEMENT: {
2233
2234
2235
2236 VariableTuple variableTuple = (VariableTuple) tuple;
2237 long offset = variableTuple.getVariableOffset();
2238 boolean isGlobal = variableTuple.isGlobal();
2239 Map<Object, Object> aa = getMapVariable(offset, isGlobal);
2240 Object key = pop();
2241 checkScalar(key);
2242 if (aa != null) {
2243 aa.remove(key);
2244 }
2245 position.next();
2246 break;
2247 }
2248 case DELETE_MAP_ELEMENT: {
2249
2250
2251 Object key = pop();
2252 checkScalar(key);
2253 Map<Object, Object> aa = toMap(pop());
2254 aa.remove(key);
2255 position.next();
2256 break;
2257 }
2258 case DELETE_ARRAY: {
2259
2260
2261
2262 VariableTuple variableTuple = (VariableTuple) tuple;
2263 long offset = variableTuple.getVariableOffset();
2264 boolean isGlobal = variableTuple.isGlobal();
2265 Map<Object, Object> array = getMapVariable(offset, isGlobal);
2266 if (array != null) {
2267 array.clear();
2268 }
2269 position.next();
2270 break;
2271 }
2272 case SET_EXIT_ADDRESS: {
2273
2274 exitAddress = tuple.getAddress();
2275 position.next();
2276 break;
2277 }
2278 case SET_WITHIN_END_BLOCKS: {
2279
2280 BooleanTuple endBlocksTuple = (BooleanTuple) tuple;
2281 withinEndBlocks = endBlocksTuple.getValue();
2282 position.next();
2283 break;
2284 }
2285 case EXIT_WITHOUT_CODE:
2286 case EXIT_WITH_CODE: {
2287 if (opcode == Opcode.EXIT_WITH_CODE) {
2288
2289 exitCode = (int) JRT.toDouble(pop());
2290 }
2291 throwExitException = true;
2292
2293
2294 if (!withinEndBlocks && exitAddress != null) {
2295
2296 runtimeStack.popAllFrames();
2297
2298 operandStack.clear();
2299 position.jump(exitAddress);
2300 } else {
2301
2302
2303 operandStack.clear();
2304 throw new ExitException(exitCode, "The AWK script requested an exit");
2305
2306 }
2307 break;
2308 }
2309 case REGEXP: {
2310
2311 RegexTuple regexTuple = (RegexTuple) tuple;
2312 Pattern pattern = regexTuple.getPattern();
2313 push(pattern);
2314 position.next();
2315 break;
2316 }
2317 case CONDITION_PAIR: {
2318
2319
2320 if (conditionPairs == null) {
2321 conditionPairs = new HashMap<Integer, ConditionPair>();
2322 }
2323 int currentIndex = position.currentIndex();
2324 ConditionPair cp = conditionPairs.get(currentIndex);
2325 if (cp == null) {
2326 cp = new ConditionPair();
2327 conditionPairs.put(currentIndex, cp);
2328 }
2329 boolean end = jrt.toBoolean(pop());
2330 boolean start = jrt.toBoolean(pop());
2331 push(cp.update(start, end) ? ONE : ZERO);
2332 position.next();
2333 break;
2334 }
2335 case IS_IN: {
2336
2337 Object arr = pop();
2338 Object arg = pop();
2339 checkScalar(arg);
2340 if (arr == null || arr instanceof UninitializedObject) {
2341 push(ZERO);
2342 position.next();
2343 break;
2344 }
2345 if (!(arr instanceof Map)) {
2346 throw new AwkRuntimeException("Attempting to test membership on a non-associative-array.");
2347 }
2348 @SuppressWarnings("unchecked")
2349 Map<Object, Object> aa = (Map<Object, Object>) arr;
2350 boolean result = JRT.containsAwkKey(aa, arg);
2351 push(result ? ONE : ZERO);
2352 position.next();
2353 break;
2354 }
2355 case THIS: {
2356
2357
2358
2359
2360 position.next();
2361 break;
2362 }
2363 case EXTENSION: {
2364
2365
2366
2367
2368
2369
2370
2371 ExtensionTuple extensionTuple = (ExtensionTuple) tuple;
2372 ExtensionFunction function = extensionTuple.getFunction();
2373 long numArgs = extensionTuple.getArgCount();
2374 boolean isInitial = extensionTuple.isInitial();
2375
2376 Object[] args = new Object[(int) numArgs];
2377 for (int i = (int) numArgs - 1; i >= 0; i--) {
2378 args[i] = pop();
2379 }
2380
2381 String extensionClassName = function.getExtensionClassName();
2382 JawkExtension extension = extensionInstances.get(extensionClassName);
2383 if (extension == null) {
2384 throw new AwkRuntimeException(
2385 position.lineNumber(),
2386 "Extension instance for class '" + extensionClassName
2387 + "' is not registered");
2388 }
2389 if (!(extension instanceof AbstractExtension)) {
2390 throw new AwkRuntimeException(
2391 position.lineNumber(),
2392 "Extension instance for class '" + extensionClassName
2393 + "' does not extend "
2394 + AbstractExtension.class.getName());
2395 }
2396
2397 Object retval = function.invoke((AbstractExtension) extension, args);
2398
2399
2400
2401
2402 if (isInitial && retval != null && retval instanceof BlockObject) {
2403 retval = new BlockManager().block((BlockObject) retval);
2404 }
2405
2406
2407 if (retval == null) {
2408 retval = "";
2409 } else
2410 if (!(retval instanceof Integer
2411 ||
2412 retval instanceof Long
2413 ||
2414 retval instanceof Double
2415 ||
2416 retval instanceof String
2417 ||
2418 retval instanceof Map
2419 ||
2420 retval instanceof BlockObject)) {
2421
2422
2423 retval = retval.toString();
2424 }
2425 push(retval);
2426
2427 position.next();
2428 break;
2429 }
2430 case ASSIGN_NF: {
2431 Object v = pop();
2432 jrt.setNF(v);
2433 push(v);
2434 position.next();
2435 break;
2436 }
2437 case PUSH_NF: {
2438 push(jrt.getNF());
2439 position.next();
2440 break;
2441 }
2442 case ASSIGN_NR: {
2443 Object v = pop();
2444 jrt.setNR(v);
2445 push(v);
2446 position.next();
2447 break;
2448 }
2449 case PUSH_NR: {
2450 push(jrt.getNR());
2451 position.next();
2452 break;
2453 }
2454 case ASSIGN_FNR: {
2455 Object v = pop();
2456 jrt.setFNR(v);
2457 push(v);
2458 position.next();
2459 break;
2460 }
2461 case PUSH_FNR: {
2462 push(jrt.getFNR());
2463 position.next();
2464 break;
2465 }
2466 case ASSIGN_FS: {
2467 Object v = pop();
2468 jrt.setFS(v);
2469 push(v);
2470 position.next();
2471 break;
2472 }
2473 case PUSH_FS: {
2474 push(jrt.getFSVar());
2475 position.next();
2476 break;
2477 }
2478 case ASSIGN_RS: {
2479 Object v = pop();
2480 jrt.setRS(v);
2481 push(v);
2482 position.next();
2483 break;
2484 }
2485 case PUSH_RS: {
2486 push(jrt.getRSVar());
2487 position.next();
2488 break;
2489 }
2490 case ASSIGN_OFS: {
2491 Object v = pop();
2492 jrt.setOFS(v);
2493 push(v);
2494 position.next();
2495 break;
2496 }
2497 case PUSH_OFS: {
2498 push(jrt.getOFSVar());
2499 position.next();
2500 break;
2501 }
2502 case ASSIGN_ORS: {
2503 Object v = pop();
2504 jrt.setORS(v);
2505 push(v);
2506 position.next();
2507 break;
2508 }
2509 case PUSH_ORS: {
2510 push(jrt.getORSVar());
2511 position.next();
2512 break;
2513 }
2514 case ASSIGN_RSTART: {
2515 Object v = pop();
2516 jrt.setRSTART(v);
2517 push(v);
2518 position.next();
2519 break;
2520 }
2521 case PUSH_RSTART: {
2522 push(jrt.getRSTART());
2523 position.next();
2524 break;
2525 }
2526 case ASSIGN_RLENGTH: {
2527 Object v = pop();
2528 jrt.setRLENGTH(v);
2529 push(v);
2530 position.next();
2531 break;
2532 }
2533 case PUSH_RLENGTH: {
2534 push(jrt.getRLENGTH());
2535 position.next();
2536 break;
2537 }
2538 case ASSIGN_FILENAME: {
2539 Object v = pop();
2540 jrt.setFILENAMEViaJrt(v == null ? "" : v.toString());
2541 push(v == null ? "" : v.toString());
2542 position.next();
2543 break;
2544 }
2545 case PUSH_FILENAME: {
2546 push(jrt.getFILENAME());
2547 position.next();
2548 break;
2549 }
2550 case ASSIGN_SUBSEP: {
2551 Object v = pop();
2552 jrt.setSUBSEP(v);
2553 push(v);
2554 position.next();
2555 break;
2556 }
2557 case PUSH_SUBSEP: {
2558 push(jrt.getSUBSEPVar());
2559 position.next();
2560 break;
2561 }
2562 case ASSIGN_CONVFMT: {
2563 Object v = pop();
2564 jrt.setCONVFMT(v);
2565 push(v);
2566 position.next();
2567 break;
2568 }
2569 case PUSH_CONVFMT: {
2570 push(jrt.getCONVFMTVar());
2571 position.next();
2572 break;
2573 }
2574 case ASSIGN_OFMT: {
2575 Object v = pop();
2576 jrt.setOFMT(v);
2577 push(v);
2578 position.next();
2579 break;
2580 }
2581 case PUSH_OFMT: {
2582 push(getOFMT());
2583 position.next();
2584 break;
2585 }
2586 case ASSIGN_ARGC: {
2587 Object v = pop();
2588 if (argcOffset == NULL_OFFSET) {
2589 throw new AwkRuntimeException("ARGC is read-only (not materialized).");
2590 }
2591 runtimeStack.setVariable(argcOffset, v, true);
2592 push(v);
2593 position.next();
2594 break;
2595 }
2596 case PUSH_ARGC: {
2597 if (argcOffset == NULL_OFFSET) {
2598 push(getARGC());
2599 } else {
2600 push(runtimeStack.getVariable(argcOffset, true));
2601 }
2602 position.next();
2603 break;
2604 }
2605 default:
2606 throw new Error("invalid opcode: " + position.opcode());
2607 }
2608 if (profiling) {
2609 afterProfiledTuple(opcode, tupleStartNanos);
2610 }
2611 }
2612
2613 } catch (ExitException ee) {
2614 if (profiling && (opcode == Opcode.EXIT_WITH_CODE || opcode == Opcode.EXIT_WITHOUT_CODE)) {
2615 afterProfiledTuple(opcode, tupleStartNanos);
2616 }
2617 throw ee;
2618 } catch (IOException ioe) {
2619
2620 runtimeStack.popAllFrames();
2621
2622 operandStack.clear();
2623 throw ioe;
2624 } catch (RuntimeException re) {
2625
2626 runtimeStack.popAllFrames();
2627
2628 operandStack.clear();
2629 if (re instanceof AwkSandboxException) {
2630 throw re;
2631 }
2632 throw new AwkRuntimeException(position.lineNumber(), re.getMessage(), re);
2633 } catch (AssertionError ae) {
2634
2635 runtimeStack.popAllFrames();
2636
2637 operandStack.clear();
2638 throw ae;
2639 }
2640
2641
2642 if (throwExitException) {
2643 throw new ExitException(exitCode, "The AWK script requested an exit");
2644 }
2645 }
2646
2647
2648
2649
2650 public void resetProfiling() {
2651 if (!profiling) {
2652 return;
2653 }
2654 tupleProfilingStats.clear();
2655 functionProfilingStats.clear();
2656 activeProfilingFunctions.clear();
2657 }
2658
2659
2660
2661
2662
2663
2664 public ProfilingReport getProfilingReport() {
2665 if (!profiling) {
2666 return ProfilingReport.empty();
2667 }
2668 return new ProfilingReport(tupleProfilingStats, functionProfilingStats);
2669 }
2670
2671 private void execPrint(CountTuple tuple) throws IOException {
2672 long numArgs = tuple.getCount();
2673 jrt.printDefault(numArgs == 0 ? new Object[] { jrt.jrtGetInputField(0) } : popArguments(numArgs));
2674 }
2675
2676 private void execPrintToFile(CountAndAppendTuple tuple) throws IOException {
2677 String key = jrt.toAwkString(pop());
2678 long numArgs = tuple.getCount();
2679 jrt
2680 .printToFile(
2681 key,
2682 tuple.isAppend(),
2683 numArgs == 0 ? new Object[]
2684 { jrt.jrtGetInputField(0) } : popArguments(numArgs));
2685 }
2686
2687 private void execPrintToPipe(CountTuple tuple) throws IOException {
2688 String cmd = jrt.toAwkString(pop());
2689 long numArgs = tuple.getCount();
2690 jrt.printToProcess(cmd, numArgs == 0 ? new Object[] { jrt.jrtGetInputField(0) } : popArguments(numArgs));
2691 }
2692
2693 private void execPrintf(CountTuple tuple) throws IOException {
2694 long numArgs = tuple.getCount();
2695 Object[] values = popArguments(numArgs - 1);
2696 String format = jrt.toAwkString(pop());
2697 jrt.printfDefault(format, values);
2698 }
2699
2700 private void execPrintfToFile(CountAndAppendTuple tuple) throws IOException {
2701 String key = jrt.toAwkString(pop());
2702 long numArgs = tuple.getCount();
2703 Object[] values = popArguments(numArgs - 1);
2704 String format = jrt.toAwkString(pop());
2705 jrt.printfToFile(key, tuple.isAppend(), format, values);
2706 }
2707
2708 private void execPrintfToPipe(CountTuple tuple) throws IOException {
2709 String cmd = jrt.toAwkString(pop());
2710 long numArgs = tuple.getCount();
2711 Object[] values = popArguments(numArgs - 1);
2712 String format = jrt.toAwkString(pop());
2713 jrt.printfToProcess(cmd, format, values);
2714 }
2715
2716 private void execLength(CountTuple tuple) {
2717 long num = tuple.getCount();
2718 if (num == 0) {
2719 push(jrt.jrtGetInputField(0).toString().length());
2720 return;
2721 }
2722 Object value = pop();
2723 if (value instanceof Map) {
2724 push((long) ((Map<?, ?>) value).size());
2725 } else {
2726 push(jrt.toAwkString(value).length());
2727 }
2728 }
2729
2730 private void execMatch() {
2731 String ere = jrt.toAwkString(pop());
2732 String s = jrt.toAwkString(pop());
2733 int flags = 0;
2734 if (globalVariableOffsets.containsKey("IGNORECASE")) {
2735 Integer offsetObj = globalVariableOffsets.get("IGNORECASE");
2736 Object ignorecase = runtimeStack.getVariable(offsetObj, true);
2737 if (JRT.toDouble(ignorecase) != 0) {
2738 flags |= Pattern.CASE_INSENSITIVE;
2739 }
2740 }
2741 Pattern pattern = Pattern.compile(ere, flags);
2742 Matcher matcher = pattern.matcher(s);
2743 if (matcher.find()) {
2744 int start = matcher.start() + 1;
2745 int len = matcher.end() - matcher.start();
2746 jrt.setRSTART(start);
2747 jrt.setRLENGTH(len);
2748 push(start);
2749 } else {
2750 jrt.setRSTART(0);
2751 jrt.setRLENGTH(-1);
2752 push(0);
2753 }
2754 }
2755
2756 private void execSubForDollar0(BooleanTuple tuple) {
2757 boolean isGsub = tuple.getValue();
2758 String repl = jrt.toAwkString(pop());
2759 String ere = jrt.toAwkString(pop());
2760 String orig = jrt.toAwkString(jrt.jrtGetInputField(0));
2761 String newstring = isGsub ? replaceAll(orig, ere, repl) : replaceFirst(orig, ere, repl);
2762 jrt.setInputLine(newstring);
2763 jrt.jrtParseFields();
2764 }
2765
2766 private void execSubForDollarReference(BooleanTuple tuple) {
2767 boolean isGsub = tuple.getValue();
2768 long fieldNum = JRT.parseFieldNumber(pop());
2769 String orig = jrt.toAwkString(pop());
2770 String repl = jrt.toAwkString(pop());
2771 String ere = jrt.toAwkString(pop());
2772 String newstring = isGsub ? replaceAll(orig, ere, repl) : replaceFirst(orig, ere, repl);
2773 if (fieldNum == 0) {
2774 jrt.setInputLine(newstring);
2775 jrt.jrtParseFields();
2776 } else {
2777 jrt.jrtSetInputField(newstring, fieldNum);
2778 }
2779 }
2780
2781 private void execSubForVariable(SubstitutionVariableTuple tuple, PositionTracker position) {
2782 String newString = execSubOrGSub(tuple.isGlobalSubstitution());
2783 assign(tuple.getVariableOffset(), newString, tuple.isGlobal(), position);
2784 pop();
2785 }
2786
2787 private void execSubForArrayReference(SubstitutionVariableTuple tuple) {
2788 Object arrIdx = pop();
2789 String newString = execSubOrGSub(tuple.isGlobalSubstitution());
2790 assignArray(tuple.getVariableOffset(), arrIdx, newString, tuple.isGlobal());
2791 pop();
2792 }
2793
2794 private void execSubForMapReference(BooleanTuple tuple) {
2795 Object arrIdx = pop();
2796 Map<Object, Object> array = toMap(pop());
2797 String newString = execSubOrGSub(tuple.getValue());
2798 assignMapElement(array, arrIdx, newString);
2799 pop();
2800 }
2801
2802 private void execSplit(CountTuple tuple, PositionTracker position) {
2803 long numArgs = tuple.getCount();
2804 String fsString;
2805 if (numArgs == 2) {
2806 fsString = jrt.toAwkString(jrt.getFSVar());
2807 } else if (numArgs == 3) {
2808 fsString = jrt.toAwkString(pop());
2809 } else {
2810 throw new Error("Invalid # of args. split() requires 2 or 3. Got: " + numArgs);
2811 }
2812 Object o = pop();
2813 if (!(o instanceof Map)) {
2814 throw new AwkRuntimeException(position.lineNumber(), o + " is not an array.");
2815 }
2816 String s = jrt.toAwkString(pop());
2817 Enumeration<Object> tokenizer;
2818 if (fsString.equals(" ")) {
2819 tokenizer = new StringTokenizer(s);
2820 } else if (fsString.length() == 1) {
2821 tokenizer = new SingleCharacterTokenizer(s, fsString.charAt(0));
2822 } else if (fsString.isEmpty()) {
2823 tokenizer = new CharacterTokenizer(s);
2824 } else {
2825 tokenizer = new RegexTokenizer(s, fsString);
2826 }
2827
2828 @SuppressWarnings("unchecked")
2829 Map<Object, Object> assocArray = (Map<Object, Object>) o;
2830 assocArray.clear();
2831 long cnt = 0;
2832 while (tokenizer.hasMoreElements()) {
2833 assocArray.put(++cnt, tokenizer.nextElement());
2834 }
2835 push(cnt);
2836 }
2837
2838 private void execSubstr(CountTuple tuple) {
2839 long numArgs = tuple.getCount();
2840 int startPos, length;
2841 String s;
2842 if (numArgs == 3) {
2843 length = (int) JRT.toLong(pop());
2844 startPos = (int) JRT.toDouble(pop());
2845 s = jrt.toAwkString(pop());
2846 } else if (numArgs == 2) {
2847 startPos = (int) JRT.toDouble(pop());
2848 s = jrt.toAwkString(pop());
2849 length = s.length() - startPos + 1;
2850 } else {
2851 throw new Error("numArgs for SUBSTR must be 2 or 3. It is " + numArgs);
2852 }
2853 if (startPos <= 0) {
2854 startPos = 1;
2855 }
2856 if (length <= 0 || startPos > s.length()) {
2857 push(BLANK);
2858 } else if (startPos + length > s.length()) {
2859 push(s.substring(startPos - 1));
2860 } else {
2861 push(s.substring(startPos - 1, startPos + length - 1));
2862 }
2863 }
2864
2865 private void execSetNumGlobals(CountTuple tuple) {
2866 long numGlobals = tuple.getCount();
2867 Object[] globals = runtimeStack.getNumGlobals();
2868 if (mergedGlobalLayoutActive) {
2869 if (!hasCompatiblePersistentGlobalLayout(numGlobals)) {
2870 throw new IllegalStateException(
2871 "AVM globals are already initialized for an incompatible persistent layout.");
2872 }
2873 applyExecutionInitialVariablesToGlobalSlots(true);
2874 } else if (globals == null) {
2875 runtimeStack.setNumGlobals(numGlobals, globalVariableOffsets);
2876 initializedEvalGlobalVariableOffsets = globalVariableOffsets;
2877 initializedEvalGlobalVariableArrays = globalVariableArrays;
2878 applyExecutionInitialVariablesToGlobalSlots(false);
2879 } else if (!hasCompatibleEvalGlobalLayout(numGlobals)) {
2880 throw new IllegalStateException(
2881 "AVM globals are already initialized for a different eval layout. Call prepareForEval(...) first.");
2882 }
2883 }
2884
2885 private void execApplySubsep(CountTuple tuple) {
2886 long count = tuple.getCount();
2887 if (count == 1) {
2888 Object value = pop();
2889 checkScalar(value);
2890 push(jrt.toAwkString(value));
2891 return;
2892 }
2893 StringBuilder sb = new StringBuilder();
2894 Object value = pop();
2895 checkScalar(value);
2896 sb.append(jrt.toAwkString(value));
2897 String subsep = jrt.toAwkString(jrt.getSUBSEPVar());
2898 for (int i = 1; i < count; i++) {
2899 sb.insert(0, subsep);
2900 value = pop();
2901 checkScalar(value);
2902 sb.insert(0, jrt.toAwkString(value));
2903 }
2904 push(sb.toString());
2905 }
2906
2907 private long beforeProfiledTuple(Tuple tuple, Opcode opcode) {
2908 long now = System.nanoTime();
2909 if (opcode == Opcode.CALL_FUNCTION) {
2910 CallFunctionTuple callTuple = (CallFunctionTuple) tuple;
2911 activeProfilingFunctions.push(new ActiveFunction(callTuple.getFunctionName(), now));
2912 } else if (opcode == Opcode.EXTENSION) {
2913 ExtensionTuple extensionTuple = (ExtensionTuple) tuple;
2914 ExtensionFunction function = extensionTuple.getFunction();
2915 activeProfilingFunctions.push(new ActiveFunction(function.getKeyword(), now));
2916 }
2917 return now;
2918 }
2919
2920 private void afterProfiledTuple(Opcode opcode, long tupleStartNanos) {
2921 long now = System.nanoTime();
2922 statisticsFor(tupleProfilingStats, opcode).add(now - tupleStartNanos);
2923 if (opcode == Opcode.EXIT_WITH_CODE || opcode == Opcode.EXIT_WITHOUT_CODE) {
2924 recordAllFunctionExits(now);
2925 } else if (opcode == Opcode.EXTENSION || opcode == Opcode.RETURN_FROM_FUNCTION) {
2926 recordFunctionExit(now);
2927 }
2928 }
2929
2930 private static <K> ProfilingReport.Accumulator statisticsFor(
2931 Map<K, ProfilingReport.Accumulator> stats,
2932 K key) {
2933 ProfilingReport.Accumulator accumulator = stats.get(key);
2934 if (accumulator == null) {
2935 accumulator = new ProfilingReport.Accumulator();
2936 stats.put(key, accumulator);
2937 }
2938 return accumulator;
2939 }
2940
2941 private void recordFunctionExit(long now) {
2942 if (activeProfilingFunctions.isEmpty()) {
2943 return;
2944 }
2945 ActiveFunction function = activeProfilingFunctions.pop();
2946 statisticsFor(functionProfilingStats, function.name).add(now - function.startNanos);
2947 }
2948
2949 private void recordAllFunctionExits(long now) {
2950 while (!activeProfilingFunctions.isEmpty()) {
2951 recordFunctionExit(now);
2952 }
2953 }
2954
2955 private static final class ActiveFunction {
2956 private final String name;
2957 private final long startNanos;
2958
2959 private ActiveFunction(String name, long startNanos) {
2960 this.name = name;
2961 this.startNanos = startNanos;
2962 }
2963 }
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976 @Override
2977 public void close() throws IOException {
2978 jrt.jrtCloseAll();
2979 closeResolvedInputSource();
2980 resolvedInputSource = null;
2981 inputSourceFilelistAssignmentsApplied = false;
2982 }
2983
2984
2985
2986
2987
2988
2989
2990 private void closeResolvedInputSource() {
2991 closeInputSource(resolvedInputSource);
2992 }
2993
2994 private void closeInputSource(InputSource inputSource) {
2995 if (!(inputSource instanceof Closeable)) {
2996 return;
2997 }
2998 try {
2999 ((Closeable) inputSource).close();
3000 } catch (IOException ignored) {
3001
3002 }
3003 }
3004
3005 private Object[] popArguments(long numArgs) {
3006 Object[] args = new Object[(int) numArgs];
3007 for (int i = (int) numArgs - 1; i >= 0; i--) {
3008 args[i] = pop();
3009 }
3010 return args;
3011 }
3012
3013
3014
3015
3016 private String sprintfFunction(long numArgs) {
3017 Object[] argArray = popArguments(numArgs - 1);
3018 String fmt = jrt.toAwkString(pop());
3019 return jrt.getAwkSink().sprintf(fmt, argArray);
3020 }
3021
3022 private void setNumOnJRT(long fieldNum, double num) {
3023 String numString;
3024 if (JRT.isActuallyLong(num)) {
3025 numString = Long.toString((long) Math.rint(num));
3026 } else {
3027 numString = Double.toString(num);
3028 }
3029
3030
3031 if (fieldNum == 0) {
3032 jrt.setInputLine(numString.toString());
3033 jrt.jrtParseFields();
3034 } else {
3035 jrt.jrtSetInputField(numString, fieldNum);
3036 }
3037 }
3038
3039 private String execSubOrGSub(boolean isGsub) {
3040 String newString;
3041
3042
3043
3044
3045 String orig = jrt.toAwkString(pop());
3046 String repl = jrt.toAwkString(pop());
3047 String ere = jrt.toAwkString(pop());
3048 if (isGsub) {
3049 newString = replaceAll(orig, ere, repl);
3050 } else {
3051 newString = replaceFirst(orig, ere, repl);
3052 }
3053
3054 return newString;
3055 }
3056
3057 private StringBuffer replaceFirstSb = new StringBuffer();
3058
3059
3060
3061
3062 private String replaceFirst(String orig, String ere, String repl) {
3063 push(RegexRuntimeSupport.replaceFirst(orig, repl, ere, replaceFirstSb));
3064 return replaceFirstSb.toString();
3065 }
3066
3067 private StringBuffer replaceAllSb = new StringBuffer();
3068
3069
3070
3071
3072 private String replaceAll(String orig, String ere, String repl) {
3073 push(RegexRuntimeSupport.replaceAll(orig, repl, ere, replaceAllSb));
3074 return replaceAllSb.toString();
3075 }
3076
3077
3078
3079
3080 private void assign(long l, Object value, boolean isGlobal, PositionTracker position) {
3081
3082 if (runtimeStack.getVariable(l, isGlobal) instanceof Map) {
3083 throw new AwkRuntimeException(position.lineNumber(), "cannot assign anything to an unindexed associative array");
3084 }
3085 push(value);
3086 runtimeStack.setVariable(l, value, isGlobal);
3087
3088 }
3089
3090
3091
3092
3093 private void assignArray(long offset, Object arrIdx, Object rhs, boolean isGlobal) {
3094 assignMapElement(ensureMapVariable(offset, isGlobal), arrIdx, rhs);
3095 }
3096
3097 private void assignMapElement(Map<Object, Object> array, Object arrIdx, Object rhs) {
3098 checkScalar(arrIdx);
3099 array.put(arrIdx, rhs);
3100 push(rhs);
3101 }
3102
3103
3104
3105
3106
3107 private Object inc(long l, boolean isGlobal) {
3108 Object o = runtimeStack.getVariable(l, isGlobal);
3109 if (o == null || o instanceof UninitializedObject) {
3110 o = ZERO;
3111 runtimeStack.setVariable(l, o, isGlobal);
3112 }
3113 Object updated = JRT.inc(o);
3114 runtimeStack.setVariable(l, updated, isGlobal);
3115 return o;
3116 }
3117
3118
3119
3120
3121
3122 private Object dec(long l, boolean isGlobal) {
3123 Object o = runtimeStack.getVariable(l, isGlobal);
3124 if (o == null || o instanceof UninitializedObject) {
3125 o = ZERO;
3126 runtimeStack.setVariable(l, o, isGlobal);
3127 }
3128 Object updated = JRT.dec(o);
3129 runtimeStack.setVariable(l, updated, isGlobal);
3130 return o;
3131 }
3132
3133
3134 @Override
3135 public final Object getRS() {
3136 return jrt.getRSVar();
3137 }
3138
3139
3140 @Override
3141 public final Object getOFS() {
3142 return jrt.getOFSVar();
3143 }
3144
3145 public final Object getORS() {
3146 return jrt.getORSVar();
3147 }
3148
3149
3150 @Override
3151 public final Object getSUBSEP() {
3152 return jrt.getSUBSEPVar();
3153 }
3154
3155
3156
3157
3158
3159
3160
3161
3162 @SuppressWarnings("unused")
3163 private void setFilelistVariable(String nameValue) {
3164 NameValueAssignment assignment = parseNameValueAssignment(nameValue);
3165 String name = assignment.name;
3166 Object obj = assignment.value;
3167
3168
3169 if (functionNames.contains(name)) {
3170 throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + name + ").");
3171 }
3172
3173 Integer offsetObj = globalVariableOffsets.get(name);
3174 Boolean arrayObj = globalVariableArrays.get(name);
3175
3176 if (offsetObj != null) {
3177 if (arrayObj.booleanValue()) {
3178 throw new IllegalArgumentException("Cannot assign a scalar to a non-scalar variable (" + name + ").");
3179 } else {
3180 runtimeStack.setFilelistVariable(offsetObj.intValue(), obj);
3181 }
3182 } else if (runtimeStack.hasGlobalVariable(name)) {
3183 runtimeStack.setGlobalVariable(name, obj);
3184 }
3185 }
3186
3187
3188 @Override
3189 public final void assignVariable(String name, Object obj) {
3190
3191
3192 if (globalVariableOffsets == null || globalVariableArrays == null) {
3193 Object normalized = normalizeVariableValue(obj);
3194 baseInitialVariables.put(name, normalized);
3195 if (JRT.isJrtManagedSpecialVariable(name)) {
3196 baseSpecialVariables.put(name, normalized);
3197 }
3198 return;
3199 }
3200
3201
3202 if (functionNames.contains(name)) {
3203 throw new IllegalArgumentException("Cannot assign a scalar to a function name (" + name + ").");
3204 }
3205
3206 Integer offsetObj = globalVariableOffsets.get(name);
3207 Boolean arrayObj = globalVariableArrays.get(name);
3208
3209 if (offsetObj != null) {
3210 Object normalized = normalizeVariableValue(obj);
3211 if (arrayObj.booleanValue()) {
3212 if (normalized instanceof Map) {
3213 runtimeStack.setFilelistVariable(offsetObj.intValue(), normalized);
3214 } else {
3215 throw new IllegalArgumentException(
3216 "Cannot assign a scalar to a non-scalar variable (" + name + ").");
3217 }
3218 } else {
3219 runtimeStack.setFilelistVariable(offsetObj.intValue(), normalized);
3220 }
3221 } else if (runtimeStack.hasGlobalVariable(name)) {
3222 Object normalized = normalizeVariableValue(obj);
3223 runtimeStack.setGlobalVariable(name, normalized);
3224 }
3225 }
3226
3227 private void applyInputSourceFilelistAssignmentsIfNeeded() {
3228 if (inputSourceFilelistAssignmentsApplied || resolvedInputSource instanceof StreamInputSource) {
3229 return;
3230 }
3231 for (String argument : arguments) {
3232 if (argument.indexOf('=') > 0) {
3233 setFilelistVariable(argument);
3234 }
3235 }
3236 inputSourceFilelistAssignmentsApplied = true;
3237 }
3238
3239
3240 @Override
3241 public Object getFS() {
3242 return jrt.getFSVar();
3243 }
3244
3245
3246 @Override
3247 public Object getCONVFMT() {
3248 return jrt.getCONVFMTString();
3249 }
3250
3251
3252 @Override
3253 public void resetFNR() {
3254 jrt.setFNR(0);
3255 }
3256
3257
3258 @Override
3259 public void incFNR() {
3260 long v = jrt.getFNR();
3261 jrt.setFNR(v + 1);
3262 }
3263
3264
3265 @Override
3266 public void incNR() {
3267 long v = jrt.getNR();
3268 jrt.setNR(v + 1);
3269 }
3270
3271
3272 @Override
3273 public void setNF(Integer newNf) {
3274 jrt.setNF(newNf);
3275 }
3276
3277
3278 @Override
3279 public void setFILENAME(String filename) {
3280 jrt.setFILENAMEViaJrt(filename);
3281 }
3282
3283
3284 @Override
3285 public Object getARGV() {
3286 if (argvOffset == NULL_OFFSET) {
3287 Map<Object, Object> argv = newAwkArray();
3288 argv.put(0L, "jawk");
3289 for (int i = 0; i < arguments.size(); i++) {
3290 argv.put(Long.valueOf(i + 1L), arguments.get(i));
3291 }
3292 return argv;
3293 }
3294 return runtimeStack.getVariable(argvOffset, true);
3295 }
3296
3297
3298 @Override
3299 public Object getARGC() {
3300 if (argcOffset == NULL_OFFSET) {
3301 return Long.valueOf(arguments.size() + 1);
3302 }
3303 return runtimeStack.getVariable(argcOffset, true);
3304 }
3305
3306 private String getOFMT() {
3307 return jrt.getOFMTString();
3308 }
3309
3310 private Map<Object, Object> newAwkArray() {
3311 return JRT.createAwkMap(sortedArrayKeys);
3312 }
3313
3314 private Map<Object, Object> ensureMapVariable(long offset, boolean isGlobal) {
3315 Object value = runtimeStack.getVariable(offset, isGlobal);
3316 if (value == null || value.equals(BLANK) || value instanceof UninitializedObject) {
3317 Map<Object, Object> map = newAwkArray();
3318 runtimeStack.setVariable(offset, map, isGlobal);
3319 return map;
3320 }
3321 return toMap(value);
3322 }
3323
3324 private Map<Object, Object> getMapVariable(long offset, boolean isGlobal) {
3325 Object value = runtimeStack.getVariable(offset, isGlobal);
3326 if (value == null || value.equals(BLANK) || value instanceof UninitializedObject) {
3327 return null;
3328 }
3329 return toMap(value);
3330 }
3331
3332
3333
3334
3335
3336
3337
3338
3339 private Map<Object, Object> toMap(Object value) {
3340 if (!(value instanceof Map)) {
3341 throw new AwkRuntimeException("Attempting to treat a scalar as an array.");
3342 }
3343 @SuppressWarnings("unchecked")
3344 Map<Object, Object> map = (Map<Object, Object>) value;
3345 return map;
3346 }
3347
3348
3349
3350
3351
3352
3353
3354
3355 private void checkScalar(Object value) {
3356 if (value instanceof Map) {
3357 throw new AwkRuntimeException("Attempting to use an array in a scalar context.");
3358 }
3359 }
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371 private Map<Object, Object> ensureArrayInArray(Map<Object, Object> map, Object key) {
3372 checkScalar(key);
3373 Object value = JRT.getAwkValue(map, key);
3374 if (value == null || value.equals(BLANK) || value instanceof UninitializedObject) {
3375 Map<Object, Object> nested = newAwkArray();
3376 map.put(key, nested);
3377 return nested;
3378 }
3379 if (!(value instanceof Map)) {
3380 throw new AwkRuntimeException("Attempting to use a scalar as an array.");
3381 }
3382 @SuppressWarnings("unchecked")
3383 Map<Object, Object> nested = (Map<Object, Object>) value;
3384 return nested;
3385 }
3386
3387 private Object normalizeVariableValue(Object value) {
3388 return AssocArray.normalizeValue(value, sortedArrayKeys);
3389 }
3390
3391 private static final UninitializedObject BLANK = new UninitializedObject();
3392
3393
3394
3395
3396
3397 private static final Set<String> NON_PERSISTENT_GLOBALS = new HashSet<>(
3398 Arrays.asList("ARGV", "ARGC", "ENVIRON", "RSTART", "RLENGTH", "IGNORECASE"));
3399
3400 private static final class NameValueAssignment {
3401 private final String name;
3402 private final Object value;
3403
3404 private NameValueAssignment(String name, Object value) {
3405 this.name = name;
3406 this.value = value;
3407 }
3408 }
3409
3410 private static final class SingleRecordInputSource implements InputSource {
3411
3412 private final String record;
3413 private boolean consumed;
3414
3415 private SingleRecordInputSource(String record) {
3416 this.record = record;
3417 }
3418
3419 @Override
3420 public boolean nextRecord() {
3421 if (consumed || record == null) {
3422 return false;
3423 }
3424 consumed = true;
3425 return true;
3426 }
3427
3428 @Override
3429 public String getRecordText() {
3430 return consumed ? record : null;
3431 }
3432
3433 @Override
3434 public List<String> getFields() {
3435 return null;
3436 }
3437
3438 @Override
3439 public boolean isFromFilenameList() {
3440 return false;
3441 }
3442 }
3443
3444
3445
3446
3447 public static final int NULL_OFFSET = -1;
3448
3449 }