-
Notifications
You must be signed in to change notification settings - Fork 110
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
Remove MacroRef from user-facing APIs #937
Merged
popematt
merged 2 commits into
amazon-ion:ion-11-encoding
from
popematt:topo-sort-macros
Sep 17, 2024
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -149,6 +149,7 @@ public IonWriter build(Appendable out) { | |
_Private_IonTextWriterBuilder_1_1 b = fillDefaults(); | ||
ManagedWriterOptions_1_1 options = new ManagedWriterOptions_1_1( | ||
false, | ||
true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗺️ Whether to invoke macros by name in TDL. See |
||
symbolInliningStrategy, | ||
LengthPrefixStrategy.NEVER_PREFIXED, | ||
// This could be made configurable. | ||
|
@@ -166,6 +167,7 @@ public IonWriter build(OutputStream out) { | |
_Private_IonTextWriterBuilder_1_1 b = fillDefaults(); | ||
ManagedWriterOptions_1_1 options = new ManagedWriterOptions_1_1( | ||
false, | ||
true, | ||
symbolInliningStrategy, | ||
LengthPrefixStrategy.NEVER_PREFIXED, | ||
// This could be made configurable. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,9 +3,9 @@ | |
package com.amazon.ion.impl.bin | ||
|
||
import com.amazon.ion.* | ||
import com.amazon.ion.SymbolTable.UNKNOWN_SYMBOL_ID | ||
import com.amazon.ion.SymbolTable.* | ||
import com.amazon.ion.impl.* | ||
import com.amazon.ion.impl._Private_IonWriter.IntTransformer | ||
import com.amazon.ion.impl._Private_IonWriter.* | ||
import com.amazon.ion.impl.bin.LengthPrefixStrategy.* | ||
import com.amazon.ion.impl.bin.SymbolInliningStrategy.* | ||
import com.amazon.ion.impl.macro.* | ||
|
@@ -14,9 +14,6 @@ | |
import java.math.BigDecimal | ||
import java.math.BigInteger | ||
import java.util.* | ||
import kotlin.collections.ArrayList | ||
import kotlin.collections.HashMap | ||
import kotlin.collections.LinkedHashMap | ||
|
||
/** | ||
* A managed writer for Ion 1.1 that is generic over whether the raw encoding is text or binary. | ||
|
@@ -158,26 +155,107 @@ | |
return sid | ||
} | ||
|
||
private fun internMacro(macro: Macro): Int { | ||
// 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 | ||
id = macrosById.size | ||
macrosById.add(macro) | ||
macroNames.add(null) | ||
newMacros[macro] = id | ||
return id | ||
/** | ||
* Checks for macro invocations in the body of a TemplateMacro and ensure that those macros are added to the | ||
* macro table. | ||
* | ||
* This is essentially a recursive, memoized, topological sort. Given a dependency graph, it runs in O(V + E) time. | ||
* Memoization is done using the macro table, so the O(V + E) cost is only paid the first time a macro is added to | ||
* the macro table. | ||
*/ | ||
private fun addMacroDependencies(macro: Macro) { | ||
macro.dependencies.forEach { | ||
if (it !in macroTable && it !in newMacros) { | ||
addMacroDependencies(it) | ||
assignMacroAddress(it) | ||
} | ||
} | ||
} | ||
|
||
/** Converts a named macro reference to an address */ | ||
private fun MacroRef.ByName.intoId(): MacroRef.ById = MacroRef.ById(macroNames.indexOf(name)) | ||
/** | ||
* Adds a named macro to the macro table | ||
* | ||
* Steps: | ||
* - If the name is not already in use... | ||
* - And the macro is already in `newMacros`... | ||
* 1. Get the address of the macro in `newMacros` | ||
* 2. Add the name to `macroNames` for the that address | ||
* 3. return the address | ||
* - Else... | ||
* 1. Add a new entry for the macro to `newMacros` and get a new address | ||
* 2. Add the name to `macroNames` for the new address | ||
* 3. Return the new address | ||
* - If the name is already in use... | ||
* - And it is associated with the same macro... | ||
* 1. Return the address associated with the name | ||
* - And it is associated with a different macro... | ||
* - This is where the managed writer take an opinion. (Or be configurable.) | ||
* - It could mangle the name | ||
* - It could remove the name from a macro in macroTable, but then it would have to immediately flush to | ||
* make sure that any prior e-expressions are still valid. In addition, we would need to re-export all | ||
* the other macros from `$ion_encoding`. | ||
* - For now, we're just throwing an Exception. | ||
* | ||
* Visible for testing. | ||
*/ | ||
internal fun getOrAssignMacroAddressAndName(name: String, macro: Macro): Int { | ||
// TODO: This is O(n), but could be O(1). | ||
var existingAddress = macroNames.indexOf(name) | ||
if (existingAddress < 0) { | ||
// Name is not already in use | ||
existingAddress = newMacros.getOrDefault(macro, -1) | ||
|
||
val address = if (existingAddress < 0) { | ||
// Macro is not in newMacros | ||
|
||
// If it's in macroTable, we can skip adding dependencies | ||
if (macro !in macroTable) addMacroDependencies(macro) | ||
// Add to newMacros and get a macro address | ||
assignMacroAddress(macro) | ||
} else { | ||
// Macro already exists in newMacros, but doesn't have a name | ||
existingAddress | ||
} | ||
// Set the name of the macro | ||
macroNames[address] = name | ||
return address | ||
} else if (macrosById[existingAddress] == macro) { | ||
// Macro already in table, and already using the same name | ||
return existingAddress | ||
} else { | ||
// Name is already in use for a different macro. | ||
// This macro may or may not be in the table under a different name, but that's | ||
// not particularly relevant unless we want to try to fall back to a different name. | ||
TODO("Name shadowing is not supported yet. Call finish() before attempting to shadow an existing macro.") | ||
} | ||
} | ||
|
||
/** | ||
* Steps for adding an anonymous macro to the macro table | ||
* 1. Check macroTable, if found, return that address | ||
* 2. Check newMacros, if found, return that address | ||
* 3. Add to newMacros, return new address | ||
* | ||
* Visible for testing | ||
*/ | ||
internal fun getOrAssignMacroAddress(macro: Macro): Int { | ||
var address = macroTable.getOrDefault(macro, -1) | ||
if (address >= 0) return address | ||
address = newMacros.getOrDefault(macro, -1) | ||
if (address >= 0) return address | ||
|
||
/** Converts a macro address to a macro name. If no name is found, returns the original address. */ | ||
private fun MacroRef.ById.intoNamed(): MacroRef = macroNames[id]?.let { MacroRef.ByName(it) } ?: this | ||
addMacroDependencies(macro) | ||
return assignMacroAddress(macro) | ||
} | ||
|
||
/** Unconditionally adds a macro to the macro table data structures and returns the new address. */ | ||
private fun assignMacroAddress(macro: Macro): Int { | ||
val address = macrosById.size | ||
macrosById.add(macro) | ||
macroNames.add(null) | ||
newMacros[macro] = address | ||
return address | ||
} | ||
|
||
// Only called by `finish()` | ||
private fun resetEncodingContext() { | ||
|
@@ -453,11 +531,20 @@ | |
} | ||
is Expression.MacroInvocation -> { | ||
stepInSExp(usingLengthPrefix = false) | ||
when (expression.address) { | ||
is MacroRef.ById -> writeInt(expression.address.id.toLong()) | ||
is MacroRef.ByName -> writeSymbol(expression.address.name) | ||
} | ||
numberOfTimesToStepOut[expression.endExclusive]++ | ||
|
||
val invokedAddress = macroTable[expression.macro] | ||
?: newMacros[expression.macro] | ||
?: throw IllegalStateException("A macro in the macro table is missing a dependency") | ||
|
||
if (options.invokeTdlMacrosByName) { | ||
val invokedName = macroNames[invokedAddress] | ||
if (invokedName != null) { | ||
writeSymbol(invokedName) | ||
return@forEachIndexed | ||
} | ||
} | ||
writeInt(invokedAddress.toLong()) | ||
} | ||
is Expression.VariableRef -> writeSymbol(macro.signature[expression.signatureIndex].variableName) | ||
else -> error("Unreachable") | ||
|
@@ -482,15 +569,6 @@ | |
TODO("Why do we need to expose this to users in the first place?") | ||
} | ||
|
||
/** | ||
* Extension function for [SymbolInliningStrategy] to accept [SymbolToken]. | ||
* Indicates whether a particular [SymbolToken] should be written inline (as opposed to writing as a SID). | ||
* Symbols with unknown text must always be written as SIDs. | ||
*/ | ||
private fun SymbolInliningStrategy.shouldWriteInline(symbolKind: SymbolKind, symbol: SymbolToken): Boolean { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗺️ This was unused. |
||
return symbol.text?.let { shouldWriteInline(symbolKind, it) } ?: false | ||
} | ||
|
||
override fun setFieldName(name: String) { | ||
handleSymbolToken(UNKNOWN_SYMBOL_ID, name, SymbolKind.FIELD_NAME, userData) | ||
} | ||
|
@@ -787,44 +865,26 @@ | |
resetEncodingContext() | ||
} | ||
|
||
override fun addMacro(macro: Macro): MacroRef { | ||
val id = internMacro(macro) | ||
return if (options.eExpressionIdentifierStrategy == ManagedWriterOptions_1_1.EExpressionIdentifierStrategy.BY_NAME) { | ||
macroNames[id] | ||
?.let { MacroRef.ByName(it) } | ||
?: MacroRef.ById(id) | ||
} else { | ||
MacroRef.ById(id) | ||
} | ||
override fun startMacro(macro: Macro) { | ||
val address = getOrAssignMacroAddress(macro) | ||
startMacro(null, address, macro) | ||
} | ||
|
||
override fun addMacro(name: String, macro: Macro): MacroRef { | ||
val id = internMacro(macro) | ||
macroNames[id] = name | ||
return if (options.eExpressionIdentifierStrategy == ManagedWriterOptions_1_1.EExpressionIdentifierStrategy.BY_NAME) { | ||
MacroRef.ByName(name) | ||
} else { | ||
MacroRef.ById(id) | ||
} | ||
override fun startMacro(name: String, macro: Macro) { | ||
val address = getOrAssignMacroAddressAndName(name, macro) | ||
startMacro(name, address, macro) | ||
} | ||
|
||
override fun startMacro(macroRef: MacroRef) { | ||
private fun startMacro(name: String?, address: Int, definition: Macro) { | ||
val useNames = options.eExpressionIdentifierStrategy == ManagedWriterOptions_1_1.EExpressionIdentifierStrategy.BY_NAME | ||
val ref = when (macroRef) { | ||
is MacroRef.ById -> if (useNames) macroRef.intoNamed() else macroRef | ||
is MacroRef.ByName -> if (useNames) macroRef else macroRef.intoId() | ||
} | ||
when (ref) { | ||
is MacroRef.ById -> userData.stepInEExp(ref.id, options.writeLengthPrefix(ContainerType.EEXP, depth + 1), macrosById[ref.id]) | ||
is MacroRef.ByName -> userData.stepInEExp(ref.name) | ||
if (useNames && name != null) { | ||
userData.stepInEExp(name) | ||
} else { | ||
val includeLengthPrefix = options.writeLengthPrefix(ContainerType.EEXP, depth + 1) | ||
userData.stepInEExp(address, includeLengthPrefix, definition) | ||
} | ||
} | ||
|
||
override fun startMacro(macro: Macro) { | ||
val id = addMacro(macro) | ||
startMacro(id) | ||
} | ||
|
||
override fun startExpressionGroup() { | ||
userData.stepInExpressionGroup(options.writeLengthPrefix(ContainerType.EXPRESSION_GROUP, depth + 1)) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
🗺️ I moved
EncodingContext
out ofMacroEvaluator
since the evaluator no longer needs to look up macros by name/address.