-
Notifications
You must be signed in to change notification settings - Fork 111
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
Rewrite MacroEvaluator to support flatten
, and container-making macros
#1019
Conversation
Codecov ReportAttention: Patch coverage is
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. |
if (isMacroInvocation()) { | ||
if (isImplicitRest && !isContainerAnExpressionGroup()) { | ||
readStreamAsExpressionGroup(expressions); | ||
return; | ||
} else if (isMacroInvocation()) { |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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() = """ |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
/** | ||
* 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 |
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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.
val firstExpression = continuation.first() | ||
firstExpression as? SeqElement ?: builder.reportSyntaxError(firstExpression, "continuation") | ||
val firstExpression = continuation.firstOrNull() | ||
firstExpression as? SeqElement ?: builder.reportSyntaxError(sexp, "continuation") |
There was a problem hiding this comment.
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.") |
There was a problem hiding this comment.
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.
// FIXME: Implicit rest args don't always work | ||
"implicit rest args" in completeTestName -> false |
There was a problem hiding this comment.
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.
_Private_FlattenStruct(-1, _systemSymbol = null, listOf(zeroToManyTagged("structs"))), | ||
_Private_MakeFieldNameAndValue(-1, _systemSymbol = null, listOf(exactlyOneTagged("fieldName"), exactlyOneTagged("value"))), |
There was a problem hiding this comment.
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.
* 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 | ||
* ``` |
There was a problem hiding this comment.
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?
// TODO: This check is O(n). Consider removing this when confident there are no double frees. | ||
check(ex !in expanderPool) | ||
expanderPool.add(ex) |
There was a problem hiding this comment.
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.
Co-authored-by: Tyler Gregg <[email protected]>
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 theMacroEvaluator
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.