diff --git a/src/main/java/com/amazon/ion/impl/macro/Macro.kt b/src/main/java/com/amazon/ion/impl/macro/Macro.kt index d791d6a5d..bac9ad60e 100644 --- a/src/main/java/com/amazon/ion/impl/macro/Macro.kt +++ b/src/main/java/com/amazon/ion/impl/macro/Macro.kt @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 package com.amazon.ion.impl.macro -import com.amazon.ion.impl.* -import com.amazon.ion.impl.macro.Macro.Parameter.Companion.exactlyOneTagged -import com.amazon.ion.impl.macro.Macro.Parameter.Companion.zeroToManyTagged +import com.amazon.ion.impl.TaglessEncoding /** * A [Macro] is either a [SystemMacro] or a [TemplateMacro]. @@ -15,13 +13,6 @@ sealed interface Macro { data class Parameter(val variableName: String, val type: ParameterEncoding, val cardinality: ParameterCardinality) { override fun toString() = "$type::$variableName${cardinality.sigil}" - - companion object { - @JvmStatic - fun zeroToManyTagged(name: String) = Parameter(name, Macro.ParameterEncoding.Tagged, Macro.ParameterCardinality.ZeroOrMore) - @JvmStatic - fun exactlyOneTagged(name: String) = Parameter(name, Macro.ParameterEncoding.Tagged, Macro.ParameterCardinality.ExactlyOne) - } } // TODO: See if we can DRY up ParameterEncoding and PrimitiveType @@ -82,63 +73,3 @@ sealed interface Macro { } } } - -/** - * Represents a template macro. A template macro is defined by a signature, and a list of template expressions. - * A template macro only gains a name and/or ID when it is added to a macro table. - */ -data class TemplateMacro(override val signature: List, val body: List) : Macro { - // TODO: Consider rewriting the body of the macro if we discover that there are any macros invoked using only - // constants as arguments—either at compile time or lazily. - // For example, the body of: (macro foo (x) (values (make_string "foo" "bar") x)) - // could be rewritten as: (values "foobar" x) - - private val cachedHashCode by lazy { signature.hashCode() * 31 + body.hashCode() } - override fun hashCode(): Int = cachedHashCode - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is TemplateMacro) return false - // Check the hashCode as a quick check before we dive into the actual data. - if (cachedHashCode != other.cachedHashCode) return false - if (signature != other.signature) return false - if (body != other.body) return false - return true - } - - override val dependencies: List by lazy { - body.filterIsInstance() - .map { it.macro } - .distinct() - } -} - -/** - * Macros that are built in, rather than being defined by a template. - */ -enum class SystemMacro(val macroName: String, override val signature: List) : Macro { - None("none", emptyList()), - Values("values", listOf(zeroToManyTagged("values"))), - Annotate("annotate", listOf(zeroToManyTagged("ann"), exactlyOneTagged("value"))), - MakeString("make_string", listOf(zeroToManyTagged("text"))), - MakeSymbol("make_symbol", listOf(zeroToManyTagged("text"))), - MakeDecimal( - "make_decimal", - listOf( - Macro.Parameter("coefficient", Macro.ParameterEncoding.CompactInt, Macro.ParameterCardinality.ExactlyOne), - Macro.Parameter("exponent", Macro.ParameterEncoding.CompactInt, Macro.ParameterCardinality.ExactlyOne), - ) - ), - - // TODO: Other system macros - - // Technically not system macros, but special forms. However, it's easier to model them as if they are macros in TDL. - IfNone("IfNone", listOf(zeroToManyTagged("stream"), zeroToManyTagged("true_branch"), zeroToManyTagged("false_branch"))), - IfSome("IfSome", listOf(zeroToManyTagged("stream"), zeroToManyTagged("true_branch"), zeroToManyTagged("false_branch"))), - IfSingle("IfSingle", listOf(zeroToManyTagged("stream"), zeroToManyTagged("true_branch"), zeroToManyTagged("false_branch"))), - IfMulti("IfMulti", listOf(zeroToManyTagged("stream"), zeroToManyTagged("true_branch"), zeroToManyTagged("false_branch"))), - ; - - override val dependencies: List - get() = emptyList() -} diff --git a/src/main/java/com/amazon/ion/impl/macro/ParameterFactory.kt b/src/main/java/com/amazon/ion/impl/macro/ParameterFactory.kt new file mode 100644 index 000000000..b0472231a --- /dev/null +++ b/src/main/java/com/amazon/ion/impl/macro/ParameterFactory.kt @@ -0,0 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package com.amazon.ion.impl.macro + +import com.amazon.ion.impl.macro.Macro.* + +/** + * Convenience functions for concisely creating [Macro.Parameter]s. + */ +object ParameterFactory { + @JvmStatic + fun zeroToManyTagged(name: String) = Parameter(name, ParameterEncoding.Tagged, ParameterCardinality.ZeroOrMore) + @JvmStatic + fun exactlyOneTagged(name: String) = Parameter(name, ParameterEncoding.Tagged, ParameterCardinality.ExactlyOne) + @JvmStatic + fun exactlyOneFlexInt(name: String) = Parameter(name, ParameterEncoding.CompactInt, ParameterCardinality.ExactlyOne) +} diff --git a/src/main/java/com/amazon/ion/impl/macro/SystemMacro.kt b/src/main/java/com/amazon/ion/impl/macro/SystemMacro.kt new file mode 100644 index 000000000..a0c473405 --- /dev/null +++ b/src/main/java/com/amazon/ion/impl/macro/SystemMacro.kt @@ -0,0 +1,57 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package com.amazon.ion.impl.macro + +import com.amazon.ion.impl.macro.ParameterFactory.exactlyOneFlexInt +import com.amazon.ion.impl.macro.ParameterFactory.exactlyOneTagged +import com.amazon.ion.impl.macro.ParameterFactory.zeroToManyTagged + +/** + * Macros that are built in, rather than being defined by a template. + */ +enum class SystemMacro(val id: Int, val macroName: String, override val signature: List) : Macro { + None(0, "none", emptyList()), + Values(1, "values", listOf(zeroToManyTagged("values"))), + Annotate(2, "annotate", listOf(zeroToManyTagged("ann"), exactlyOneTagged("value"))), + MakeString(3, "make_string", listOf(zeroToManyTagged("text"))), + MakeSymbol(4, "make_symbol", listOf(zeroToManyTagged("text"))), + MakeDecimal(6, "make_decimal", listOf(exactlyOneFlexInt("coefficient"), exactlyOneFlexInt("exponent"))), + + // TODO: Other system macros + + // Technically not system macros, but special forms. However, it's easier to model them as if they are macros in TDL. + // We give them an ID of -1 to distinguish that they are not addressable outside TDL. + IfNone(-1, "IfNone", listOf(zeroToManyTagged("stream"), zeroToManyTagged("true_branch"), zeroToManyTagged("false_branch"))), + IfSome(-1, "IfSome", listOf(zeroToManyTagged("stream"), zeroToManyTagged("true_branch"), zeroToManyTagged("false_branch"))), + IfSingle(-1, "IfSingle", listOf(zeroToManyTagged("stream"), zeroToManyTagged("true_branch"), zeroToManyTagged("false_branch"))), + IfMulti(-1, "IfMulti", listOf(zeroToManyTagged("stream"), zeroToManyTagged("true_branch"), zeroToManyTagged("false_branch"))), + ; + + override val dependencies: List + get() = emptyList() + + companion object { + + private val MACROS_BY_NAME: Map = SystemMacro.entries.associateBy { it.macroName } + + // TODO: Once all of the macros are implemented, replace this with an array as in SystemSymbols_1_1 + private val MACROS_BY_ID: Map = SystemMacro.entries + .filterNot { it.id < 0 } + .associateBy { it.id } + + @JvmStatic + fun size() = MACROS_BY_ID.size + + /** Gets a [SystemMacro] by its address in the system table */ + @JvmStatic + operator fun get(id: Int): SystemMacro? = MACROS_BY_ID[id] + + /** Gets, by name, a [SystemMacro] with an address in the system table (i.e. that can be invoked as E-Expressions) */ + @JvmStatic + operator fun get(name: String): SystemMacro? = MACROS_BY_NAME[name]?.takeUnless { it.id < 0 } + + /** Gets a [SystemMacro] by name, including those which are not in the system table (i.e. special forms) */ + @JvmStatic + fun getMacroOrSpecialForm(name: String): SystemMacro? = MACROS_BY_NAME[name] + } +} diff --git a/src/main/java/com/amazon/ion/impl/macro/TemplateMacro.kt b/src/main/java/com/amazon/ion/impl/macro/TemplateMacro.kt new file mode 100644 index 000000000..06c2be23e --- /dev/null +++ b/src/main/java/com/amazon/ion/impl/macro/TemplateMacro.kt @@ -0,0 +1,32 @@ +package com.amazon.ion.impl.macro + +/** + * Represents a template macro. A template macro is defined by a signature, and a list of template expressions. + * A template macro only gains a name and/or ID when it is added to a macro table. + */ +data class TemplateMacro(override val signature: List, val body: List) : + Macro { + // TODO: Consider rewriting the body of the macro if we discover that there are any macros invoked using only + // constants as arguments—either at compile time or lazily. + // For example, the body of: (macro foo (x) (values (make_string "foo" "bar") x)) + // could be rewritten as: (values "foobar" x) + + private val cachedHashCode by lazy { signature.hashCode() * 31 + body.hashCode() } + override fun hashCode(): Int = cachedHashCode + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is TemplateMacro) return false + // Check the hashCode as a quick check before we dive into the actual data. + if (cachedHashCode != other.cachedHashCode) return false + if (signature != other.signature) return false + if (body != other.body) return false + return true + } + + override val dependencies: List by lazy { + body.filterIsInstance() + .map { it.macro } + .distinct() + } +} diff --git a/src/test/java/com/amazon/ion/impl/EncodingDirectiveCompilationTest.java b/src/test/java/com/amazon/ion/impl/EncodingDirectiveCompilationTest.java index 9cb57a1f1..d072c8b90 100644 --- a/src/test/java/com/amazon/ion/impl/EncodingDirectiveCompilationTest.java +++ b/src/test/java/com/amazon/ion/impl/EncodingDirectiveCompilationTest.java @@ -13,6 +13,7 @@ import com.amazon.ion.impl.macro.Expression; import com.amazon.ion.impl.macro.Macro; import com.amazon.ion.impl.macro.MacroRef; +import com.amazon.ion.impl.macro.ParameterFactory; import com.amazon.ion.impl.macro.TemplateMacro; import com.amazon.ion.system.IonReaderBuilder; import org.junit.jupiter.params.ParameterizedTest; @@ -906,7 +907,7 @@ public void macroInvocationInMacroDefinition(InputType inputType) throws Excepti Macro simonSaysMacro = new TemplateMacro( Collections.singletonList( - Macro.Parameter.exactlyOneTagged("anything") + ParameterFactory.exactlyOneTagged("anything") ), Collections.singletonList( new Expression.VariableRef(0) diff --git a/src/test/java/com/amazon/ion/impl/bin/IonManagedWriter_1_1_Test.kt b/src/test/java/com/amazon/ion/impl/bin/IonManagedWriter_1_1_Test.kt index 52c642c59..bc2e2c662 100644 --- a/src/test/java/com/amazon/ion/impl/bin/IonManagedWriter_1_1_Test.kt +++ b/src/test/java/com/amazon/ion/impl/bin/IonManagedWriter_1_1_Test.kt @@ -8,6 +8,8 @@ import com.amazon.ion.impl.* import com.amazon.ion.impl.macro.* import com.amazon.ion.impl.macro.ExpressionBuilderDsl.Companion.templateBody import com.amazon.ion.impl.macro.Macro.* +import com.amazon.ion.impl.macro.ParameterFactory.exactlyOneTagged +import com.amazon.ion.impl.macro.ParameterFactory.zeroToManyTagged import com.amazon.ion.system.* import java.io.ByteArrayOutputStream import java.math.BigInteger @@ -199,8 +201,8 @@ internal class IonManagedWriter_1_1_Test { fun `write an e-expression with a expression group argument`() { val macro = TemplateMacro( signature = listOf( - Parameter.zeroToManyTagged("a"), - Parameter.zeroToManyTagged("b"), + zeroToManyTagged("a"), + zeroToManyTagged("b"), ), body = templateBody { string("foo") } ) @@ -399,14 +401,14 @@ internal class IonManagedWriter_1_1_Test { return listOf( case( "single required parameter", - signature = listOf(Parameter.exactlyOneTagged("x")), + signature = listOf(exactlyOneTagged("x")), expectedSignature = "(x)" ), case( "multiple required parameters", signature = listOf( - Parameter.exactlyOneTagged("x"), - Parameter.exactlyOneTagged("y") + exactlyOneTagged("x"), + exactlyOneTagged("y") ), expectedSignature = "(x y)" ), @@ -599,7 +601,7 @@ internal class IonManagedWriter_1_1_Test { ), case( "variable", - signature = listOf(Parameter.exactlyOneTagged("x")), + signature = listOf(exactlyOneTagged("x")), expectedSignature = "(x)", body = { variable(0) @@ -608,7 +610,7 @@ internal class IonManagedWriter_1_1_Test { ), case( "multiple variables", - signature = listOf("x", "y", "z").map(Parameter::exactlyOneTagged), + signature = listOf("x", "y", "z").map(::exactlyOneTagged), expectedSignature = "(x y z)", body = { list { @@ -695,7 +697,7 @@ internal class IonManagedWriter_1_1_Test { endMacro() startMacro( TemplateMacro( - listOf(Parameter.exactlyOneTagged("x")), + listOf(exactlyOneTagged("x")), templateBody { macro(fooMacro) { variable(0) @@ -787,7 +789,7 @@ internal class IonManagedWriter_1_1_Test { // Using the qualified class name would be verbose, but may be safer for general // use so that there is almost no risk of having a name conflict with another macro. private val MACRO_NAME = Polygon::class.simpleName!!.replace(".", "_") - private val IDENTITY = TemplateMacro(listOf(Parameter.zeroToManyTagged("x")), templateBody { variable(0) }) + private val IDENTITY = TemplateMacro(listOf(zeroToManyTagged("x")), templateBody { variable(0) }) private val MACRO = TemplateMacro( signature = listOf( // TODO: Change this to a macro shape when they are supported @@ -843,8 +845,8 @@ internal class IonManagedWriter_1_1_Test { private val MACRO_NAME = Point2D::class.simpleName!!.replace(".", "_") private val MACRO = TemplateMacro( signature = listOf( - Parameter.exactlyOneTagged("x"), - Parameter.exactlyOneTagged("y"), + exactlyOneTagged("x"), + exactlyOneTagged("y"), ), templateBody { struct {