Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@popematt
Copy link
Contributor

@popematt popematt commented Sep 6, 2024

Issue #, if available:

#733, #737, #742

Description of changes:

I've split it into multiple commits to make it easier to review.

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

@codecov
Copy link

codecov bot commented Sep 6, 2024

Codecov Report

Attention: Patch coverage is 84.72222% with 33 lines in your changes missing coverage. Please review.

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

Files with missing lines Patch % Lines
...va/com/amazon/ion/impl/bin/IonManagedWriter_1_1.kt 87.43% 9 Missing and 14 partials ⚠️
.../com/amazon/ion/impl/bin/IonRawBinaryWriter_1_1.kt 25.00% 3 Missing ⚠️
src/main/java/com/amazon/ion/IonWriter.java 0.00% 2 Missing ⚠️
...mazon/ion/impl/IonReaderContinuableCoreBinary.java 33.33% 1 Missing and 1 partial ⚠️
.../com/amazon/ion/impl/bin/SymbolInliningStrategy.kt 66.66% 2 Missing ⚠️
...rc/main/java/com/amazon/ion/impl/macro/MacroRef.kt 50.00% 1 Missing ⚠️
Additional details and impacted files
@@                Coverage Diff                 @@
##             ion-11-encoding     #934   +/-   ##
==================================================
  Coverage                   ?   69.87%           
  Complexity                 ?     6779           
==================================================
  Files                      ?      192           
  Lines                      ?    26808           
  Branches                   ?     4862           
==================================================
  Hits                       ?    18733           
  Misses                     ?     6574           
  Partials                   ?     1501           

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

@popematt popematt force-pushed the managed-macro-writer-pr branch 3 times, most recently from bfba2e3 to 770c81e Compare September 6, 2024 18:11
public void writeBlob(byte[] value, int start, int len)
throws IOException;

public default void writeObject(WriteAsIon obj) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🗺️ I'm not married to this name. Suggestions for a better name are welcome.

/**
* Writes this object to an IonWriter capable of producing macro invocations.
*/
fun writeToMacroAware(writer: MacroAwareIonWriter) = writeTo(writer as IonWriter)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🗺️ I didn't want to overload writeTo. Naming suggestions welcome here if you can think of a better one.

if (closed) return
confirm(depth() == 0) { "Cannot call finish() while in a container" }
confirm(numAnnotations == 0) { "Cannot call finish with dangling annotations" }
output.flush()
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 was the bug in the finish() (now flush()) implementation.

* TODO: What package does this really belong in?
* See also [ManagedWriterOptions], [SymbolInliningStrategy], and [DelimitedContainerStrategy].
*
* See also [ManagedWriterOptions_1_1], [SymbolInliningStrategy], and [LengthPrefixStrategy].
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🗺️ Moved so that the KDoc links will work. For some reason, KDoc links didn't seem to work when the IDE sees it's in a "TODO" item.

Comment on lines +294 to +306
// TODO: See if there's any benefit to using a smaller number type, if we can
// memoize this in the macro definition, or replace it with a list of precomputed
// step-out indices.
/** Tracks where and how many times to step out. */
val numberOfTimesToStepOut = IntArray(macro.body.size + 1)
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 just occurred to me that precomputing and/or memoizing this information in the TemplateMacro would also make it easier to break up this large method into smaller methods. The reason I didn't break this up into smaller methods is because of the awkwardness of passing around the numberOfTimesToStepOut array.

I'll leave that as a TODO for later though.

Comment on lines -382 to +387
EEXP -> presenceBitmapStack.peek().signature[currentContainer.numChildren].type.taglessEncodingKind
EEXP -> {
val signature = presenceBitmapStack.peek().signature
if (currentContainer.numChildren >= signature.size) throw IllegalArgumentException("Too many arguments for macro with signature $signature")
signature[currentContainer.numChildren].type.taglessEncodingKind
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🗺️ Previously, this would just throw an IndexOutOfBoundsException (or something like that). I ran into this unhelpful exception while I was testing things, so I put in a more useful message.

Comment on lines -44 to +47
val NEVER_INLINE = SymbolInliningStrategy { _, _ -> false }
val NEVER_INLINE = object : SymbolInliningStrategy {
override fun shouldWriteInline(symbolKind: SymbolKind, text: String): Boolean = false
override fun toString(): String = "NEVER_INLINE"
}
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 real difference here is that instead of a lambda expression, we have an anonymous class with a useful toString() implementation.

companion object {
// This is a very long macro name, but by using the qualified class name,
// there is almost no risk of having a name conflict with another macro.
private val MACRO_NAME = Point2D::class.qualifiedName!!.replace(".", "_")
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 would be even safer to replace . with $, but that was not playing nicely with the expected output in the test case because Kotlin uses $ to indicate string interpolation.

Comment on lines +711 to +701
templateBody {
struct {
fieldName("x")
variable(0)
fieldName("y")
variable(1)
}
}
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 we want people to be able to define macros for their own types, I think we do need a builder of some sort. It's so much easier to use a builder than it is to construct the expressions directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🗺️ I rearranged this file so that the Ion 1.0 symbols are first, and any added Ion 1.1 symbols come afterwards. I also added ANNOTATE, EXPORT, TDL_EXPRESSION_GROUP, LITERAL, and MAKE_SEXP.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🗺️ There is a lot of repetitive code here. Most of it is interfaces that define what methods may be called when stepped into various container types for each of DataModelExpression, TemplateBodyExpression, and EExpressionBodyExpression. This is what lets us elegantly write things like

templateBody {
    list {
        int(1)
        variable(0)
        macro("foo") {
            string("bar")
        }
    }
}

@popematt popematt requested review from tgregg and zslayton September 6, 2024 18:19
Copy link
Contributor

@zslayton zslayton left a comment

Choose a reason for hiding this comment

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

LGTM! Coupla small things to consider before merging.

Comment on lines +154 to +163
// Check the current macro table
var id = macroTable[macro]
if (id != null) return id
// Check the to-be-appended macros
id = newMacros[macro]
if (id != null) return id
// Add to the to-be-appended symbols
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this check the newMacros first so new definitions shadow the old ones?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No—at least not here—because this uses deep equality of the macro signature and body. (In practice, though, it should be short-circuited by a this === other check most of the time.)

However, your question is making me rethink the addMacro(...): MacroRef function because MacroRef could outlive an encoding context with no good way to determine whether it's still valid.

repeat(numberOfTimesToStepOut[index]) { stepOut() }
}

when (expression) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the writing logic live in the WriteAsIon impl for the various expression kinds?

Copy link
Contributor

@tgregg tgregg left a comment

Choose a reason for hiding this comment

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

Looking good!

newSymbols.forEach { (text, _) -> writeString(text) }
stepOut()
stepOut()
private fun resetEncodingContext() {
Copy link
Contributor

Choose a reason for hiding this comment

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

After this is called, how does the writer know that it needs to write a new encoding directive before the next 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.

The next time the user values are flushed, if newSymbols or newMacros is non-empty, then the managed writer will write an encoding directive before it flushes the user values. If both are empty, then we know that there are no SIDs or E-Expressions in the user values being flushed, and so no encoding directive is needed because it makes no difference.

Now that I think about it, there might be an edge case for Ion text if a system macro was being shadowed—it would end up writing incorrect data. This can be solved by making the managed writer emit an IVM if a value is written after finish() has been called.

}

@Test
fun `write an e-expression by name 2`() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this just a duplicate of the previous test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is. Oops.

if (parameter.cardinality != Macro.ParameterCardinality.ExactlyOne) {
// TODO: Consider adding a method to the raw writer that can write a single-character
// symbol without constructing a string. It might be a minor performance improvement.
// TODO: See if we can write this without a space between the parameter name and the sigil.
Copy link
Contributor

Choose a reason for hiding this comment

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

+1, I think this will be very important for readability.

@popematt popematt force-pushed the managed-macro-writer-pr branch from 770c81e to a84b4ed Compare September 7, 2024 22:33
@popematt popematt merged commit c454ecc into amazon-ion:ion-11-encoding Sep 7, 2024
@popematt popematt deleted the managed-macro-writer-pr branch September 7, 2024 23:11
popematt added a commit to popematt/ion-java that referenced this pull request Jun 19, 2025
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.

3 participants