diff --git a/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java b/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java index 86234061a..b682be5df 100644 --- a/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java +++ b/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java @@ -2045,11 +2045,18 @@ public Event stepIntoContainer() { @Override public Event stepOutOfContainer() { if (isEvaluatingEExpression) { - // TODO step out of the macro evaluator only if it's in a container. Otherwise, finish the evaluation early - // and step out of the parent. - macroEvaluatorIonReader.stepOut(); - event = Event.NEEDS_INSTRUCTION; - return event; + if (macroEvaluatorIonReader.getDepth() > 0) { + // The user has stepped into a container produced by the evaluator. Therefore, this stepOut() call + // must step out of that evaluated container. + macroEvaluatorIonReader.stepOut(); + event = Event.NEEDS_INSTRUCTION; + return event; + } else { + // The evaluator is not producing a container value. Therefore, the user intends for this stepOut() call + // to step out of the parent container of the e-expression being evaluated. This terminates e-expression + // evaluation. + isEvaluatingEExpression = false; + } } return super.stepOutOfContainer(); } diff --git a/src/test/java/com/amazon/ion/impl/IonReaderContinuableTopLevelBinaryTest.java b/src/test/java/com/amazon/ion/impl/IonReaderContinuableTopLevelBinaryTest.java index f076288dc..ad3ec07e4 100644 --- a/src/test/java/com/amazon/ion/impl/IonReaderContinuableTopLevelBinaryTest.java +++ b/src/test/java/com/amazon/ion/impl/IonReaderContinuableTopLevelBinaryTest.java @@ -6309,8 +6309,35 @@ public void earlyStepOutDelimitedStructThatContainsMacroInvocationWithTaggedExpr closeAndCount(); } - // TODO add a test that modifies the above to step out after beginning macro evaluation but before exhausting it. - // This should end evaluation early and step out of the struct. + @ParameterizedTest(name = "constructFromBytes={0}") + @ValueSource(booleans = {true, false}) + public void partialEvaluationOfMacroInvocationInDelimitedContainer(boolean constructFromBytes) throws Exception { + reader = readerForIon11(bytes( + 0xF3, // Delimited struct start + 0xFF, 'a', // FlexSym field name 'a' + 0x06, 0x02, // Macro invocation 6 (delta), AEB 2 (group) + 0x0D, // 6-byte expression group + 0x61, 0x01, // 1 + 0x61, 0x02, // 2 + 0x61, 0x03, // 3 + 0x01, 0xF0, // Delimited struct end + 0x93, 'a', 'b', 'c' + ), + constructFromBytes + ); + assertSequence( + next(IonType.STRUCT), + STEP_IN, + next(IonType.INT), + bigIntegerValue(BigInteger.ONE), + // Step out before exhausting all values produced by the 'delta' invocation. + STEP_OUT, + next(IonType.STRING), + stringValue("abc"), + next(null) + ); + closeAndCount(); + } @ParameterizedTest(name = "constructFromBytes={0}") @ValueSource(booleans = {true, false}) @@ -6452,6 +6479,69 @@ public void fillMultipleNestedDelimitedLists() throws Exception { closeAndCount(); } + @ParameterizedTest(name = "constructFromBytes={0}") + @ValueSource(booleans = {true, false}) + public void partialEvaluationOfMacroInvocationInPrefixedContainer(boolean constructFromBytes) throws Exception { + reader = readerForIon11(bytes( + 0xB9, // List length 9 + 0x06, 0x02, // Macro invocation 6 (delta), AEB 2 (group) + 0x0D, // 6-byte expression group + 0x61, 0x01, // 1 + 0x61, 0x02, // 2 + 0x61, 0x03, // 3 + 0x01, 0x02, // Macro invocation 1 (values), AEB 2 (group) + 0x09, // 4-byte expression group + 0x93, 'a', 'b', 'c' + ), + constructFromBytes + ); + assertSequence( + next(IonType.LIST), + STEP_IN, + next(IonType.INT), + bigIntegerValue(BigInteger.ONE), + // Step out before exhausting all values produced by the 'delta' invocation. + STEP_OUT, + next(IonType.STRING), + stringValue("abc"), + next(null) + ); + closeAndCount(); + } + + @ParameterizedTest(name = "constructFromBytes={0}") + @ValueSource(booleans = {true, false}) + public void earlyStepOutOfContainerInMacroInvocation(boolean constructFromBytes) throws Exception { + reader = readerForIon11(bytes( + 0x01, 0x02, // Macro invocation 1 (values), AEB 2 (group) + 0x0F, // 7-byte expression group + 0xB4, // 4-byte list + 0x61, 0x01, // 1 + 0x61, 0x02, // 2 + 0x61, 0x03, // 3 + 0x01, 0x02, // Macro invocation 1 (values), AEB 2 (group) + 0x09, // 4-byte expression group + 0x93, 'a', 'b', 'c' + ), + constructFromBytes + ); + assertSequence( + next(IonType.LIST), + STEP_IN, + next(IonType.INT), + bigIntegerValue(BigInteger.ONE), + // Step out before exhausting all values in the list. + STEP_OUT, + next(IonType.INT), + bigIntegerValue(BigInteger.valueOf(3)), + // This ends the 'values' invocation. + next(IonType.STRING), + stringValue("abc"), + next(null) + ); + closeAndCount(); + } + // TODO test continuable reading and skipping of non-prefixed macro invocations. // TODO test skipping macro invocations (especially length-prefixed ones) that expand to system values. // TODO Ion 1.1 symbol tables with all kinds of annotation encodings (opcodes E4 - E9, inline and SID)