Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite MacroEvaluator to support flatten, and container-making macros #1019

Merged

Conversation

popematt
Copy link
Contributor

@popematt popematt commented Dec 18, 2024

Issue #, if available:

#731

Description of changes:

I've added PR tour comments, indicated with a 🗺️.

There are no comments on MacroEvaluator because if there's anything there that needs further explanation, I've tried to put it in the JavaDoc. If I've forgotten anything there, it should also go in the Javadoc. I recommend looking at the MacroEvaluator files individually rather than trying to look at the diff.

At a high level, this rewrite creates something sort of like a 2-D stack, where containers are the first dimension, and macro expansions are the second dimension, so that the evaluator can look at the top of the container stack and the bottom of the macro stack in order to get the next value. See the MacroEvaluator's doc comments for more details.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copy link

codecov bot commented Dec 18, 2024

Codecov Report

Attention: Patch coverage is 82.02020% with 89 lines in your changes missing coverage. Please review.

Please upload report for BASE (ion-11-encoding@620228b). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...n/java/com/amazon/ion/impl/macro/MacroEvaluator.kt 81.11% 51 Missing and 30 partials ⚠️
...main/java/com/amazon/ion/impl/macro/Environment.kt 0.00% 5 Missing ⚠️
.../main/java/com/amazon/ion/impl/macro/Expression.kt 50.00% 2 Missing ⚠️
src/main/java/com/amazon/ion/util/Assumptions.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@                Coverage Diff                 @@
##             ion-11-encoding    #1019   +/-   ##
==================================================
  Coverage                   ?   70.81%           
  Complexity                 ?     7375           
==================================================
  Files                      ?      214           
  Lines                      ?    29533           
  Branches                   ?     5319           
==================================================
  Hits                       ?    20914           
  Misses                     ?     6960           
  Partials                   ?     1659           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment on lines -234 to +237
if (isMacroInvocation()) {
if (isImplicitRest && !isContainerAnExpressionGroup()) {
readStreamAsExpressionGroup(expressions);
return;
} else if (isMacroInvocation()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Had to change the order of some checks in this method so that it would correctly capture rest args when the first of the rest args was a macro invocation.

if (isImplicitRest && !isContainerAnExpressionGroup()) {
readStreamAsExpressionGroup(expressions);
} else if (IonType.isContainer(type)) {
if (IonType.isContainer(type) && !reader.isNullValue()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Added a check for null here because in the Expression model, all null values are treated as scalars. This was causing some of the conformance tests to fail for e.g. make_list.

@@ -21,6 +21,16 @@ data class Environment private constructor(
val parentEnvironment: Environment?,
) {
fun createChild(arguments: List<Expression>, argumentIndices: List<Int>) = Environment(arguments, argumentIndices, this)

override fun toString() = """
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ The only change in this file was adding a custom toString() implementation to help when I was debugging things.

@@ -221,21 +237,25 @@ sealed interface Expression {
*/
data class VariableRef(val signatureIndex: Int) : TemplateBodyExpression

sealed interface InvokableExpression : HasStartAndEnd, Expression {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ We're able to reduce some code duplication by unifying the code paths for TDL and E-Expression macros. This interface helps to facilitate that unification.

Comment on lines +62 to +69
/**
* Indicates to the macro evaluator that the current expansion did not produce a value this time, but it may
* produce more expressions. The macro evaluator should request another expression from that macro.
*/
data object ContinueExpansion : ExpansionOutputExpressionOrContinue

/** Signals the end of an expansion in the macro evaluator. */
data object EndOfExpansion : ExpansionOutputExpression
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ It was getting confusing to keep track of what null meant when returned from some of the internal functions of the macro evaluator. I added the singleton types ContinueExpansion and EndOfExpansion (and marker interfaces that combine them with DataModelExpression) so that we can be explicit about the result of a call to the macro expansion function instead of returning null.

@@ -560,9 +601,13 @@ class MacroEvaluatorTest {
}
}

assertIsInstance<StructValue>(evaluator.expandNext())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ This test for make_field was wrong. It was testing that the macro produced a field name and a value, but should have been testing that it produced a struct containing a single name-value pair.

Comment on lines -165 to +166
val firstExpression = continuation.first()
firstExpression as? SeqElement ?: builder.reportSyntaxError(firstExpression, "continuation")
val firstExpression = continuation.firstOrNull()
firstExpression as? SeqElement ?: builder.reportSyntaxError(sexp, "continuation")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ If there was no firstExpression, calling first() would throw NoSuchElementException, which didn't have any information about where the syntax error occurred in the conformance DSL. This change just ensures that more syntax errors are reported in a useful way.

@@ -53,6 +54,7 @@ fun TestCaseSupport.assertSignals(sexp: SeqElement, r: IonReader) {
private fun IonReader.walk(): List<String> {
val events = mutableListOf<String>()
fun recordEvent(eventType: String = type.toString(), value: Any? = "") {
if (events.size > 10_000_000) fail("Ion stream does not appear to terminate.")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Useful safeguard for when you introduce an infinite loop, or you start writing a conformance DSL test case consisting of a "Billion Laughs" attack.

Comment on lines +93 to +94
// FIXME: Implicit rest args don't always work
"implicit rest args" in completeTestName -> false
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Specifically, rest args for the if_* tests are failing. I haven't yet isolated whether this is because of an issue with those macros (unlikely), TDL in general (would also be surprising to me), or something else.

Comment on lines +31 to +32
_Private_FlattenStruct(-1, _systemSymbol = null, listOf(zeroToManyTagged("structs"))),
_Private_MakeFieldNameAndValue(-1, _systemSymbol = null, listOf(exactlyOneTagged("fieldName"), exactlyOneTagged("value"))),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ These have no integer ID and no name, so they are not possible to invoke in an Ion stream or in a user-defined template macro (because the definition of a macro that uses these cannot be serialized). They are only used for supporting make_struct and make_field respectively.

The methods of SystemMacro were also updated to never return these, so the only way to get your hands on it is by explicitly using SystemMacro._Private_.... It's not great, but it's good enough for now.

@popematt popematt marked this pull request as ready for review December 18, 2024 19:00
@popematt popematt requested a review from tgregg December 20, 2024 17:03
Comment on lines 33 to 39
* One might visualize it like this:
* ```
* 3. List : Stream --> Delta --> Variable
* 2. List : Stream --> Flatten --> Stream
* 1. Struct : Stream --> Variable --> TemplateBody --> Stream --> TemplateBody
* 0. TopLevel : Stream --> TemplateBody --> TemplateBody
* ```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More explanation here might help. By itself, I wasn't sure what it was trying to help me visualize. Should it be accompanied by some sample data?

Comment on lines 79 to 81
// TODO: This check is O(n). Consider removing this when confident there are no double frees.
check(ex !in expanderPool)
expanderPool.add(ex)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also consider using something like Set<ExpansionInfo> expanderPool = Collections.newSetFromMap(new IdentityHashMap<>()), preventing you from adding the same instance back to the pool more than once, though you'd need some extra logic to poll from the pool.

src/main/java/com/amazon/ion/impl/macro/MacroEvaluator.kt Outdated Show resolved Hide resolved
@popematt popematt merged commit f816953 into amazon-ion:ion-11-encoding Jan 2, 2025
18 of 35 checks passed
@popematt popematt deleted the evaluator-rewrite-complete branch January 2, 2025 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants