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

Adds implementations of if_* special forms #957

Merged
merged 4 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/main/java/com/amazon/ion/impl/macro/Macro.kt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ enum class SystemMacro(val macroName: String, override val signature: List<Macro
),

// 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<Macro>
Expand Down
55 changes: 53 additions & 2 deletions src/main/java/com/amazon/ion/impl/macro/MacroEvaluator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,17 @@ class MacroEvaluator {
} else {
throw IonException("Too many values for argument $argName")
}
true // Continue expansion
}
return value
}

/**
* Reads the expanded values from one argument.
*
* The callback should return true to continue the expansion or false to abandon the expansion early.
*/
fun readExpandedArgumentValues(expansionInfo: ExpansionInfo, macroEvaluator: MacroEvaluator, callback: (DataModelExpression) -> Unit) {
fun readExpandedArgumentValues(expansionInfo: ExpansionInfo, macroEvaluator: MacroEvaluator, callback: (DataModelExpression) -> Boolean) {
val i = expansionInfo.i
expansionInfo.nextSourceExpression()

Expand All @@ -74,10 +77,16 @@ class MacroEvaluator {

val depth = macroEvaluator.expansionStack.size()
var expr = macroEvaluator.expandNext(depth)
var continueExpansion: Boolean
while (expr != null) {
callback(expr)
continueExpansion = callback(expr)
if (!continueExpansion) break
expr = macroEvaluator.expandNext(depth)
}
// Step back out to the original depth (in case we exited the expansion early)
while (macroEvaluator.expansionStack.size() > depth) {
macroEvaluator.expansionStack.pop()
}
}
}

Expand Down Expand Up @@ -122,6 +131,7 @@ class MacroEvaluator {
is DataModelValue -> throw IonException("Invalid argument type for 'make_string': ${it.type}")
is FieldName -> TODO("Unreachable. We shouldn't be able to get here without first encountering a StructValue.")
}
true // continue expansion
}
return StringValue(value = sb.toString())
}
Expand All @@ -138,6 +148,7 @@ class MacroEvaluator {
is DataModelValue -> throw IonException("Invalid argument type for 'make_symbol': ${it.type}")
is FieldName -> TODO("Unreachable. We shouldn't be able to get here without first encountering a StructValue.")
}
true // continue expansion
}
return SymbolValue(value = newSymbolToken(sb.toString()))
}
Expand All @@ -158,6 +169,38 @@ class MacroEvaluator {
}
}

private enum class IfExpander(private val minInclusive: Int, private val maxExclusive: Int) : Expander {
IF_NONE(0, 1),
IF_SOME(1, -1),
IF_SINGLE(1, 2),
IF_MULTI(2, -1),
;

override fun nextExpression(expansionInfo: ExpansionInfo, macroEvaluator: MacroEvaluator): Expression {
var n = 0
readExpandedArgumentValues(expansionInfo, macroEvaluator) {
n++
println(n)
popematt marked this conversation as resolved.
Show resolved Hide resolved
// If there's no max, then we'll only continue the expansion if we haven't yet reached the min
// If there is a max, then we'll continue the expansion until we reach the max
if (maxExclusive < 0) n < minInclusive else n < maxExclusive
}
val isConditionTrue = n >= minInclusive && (maxExclusive < 0 || n < maxExclusive)
println(isConditionTrue)
popematt marked this conversation as resolved.
Show resolved Hide resolved
// Save the current expansion index. This is the index of the "true" expression
val trueExpressionPosition = expansionInfo.i
// Now we are positioned on the "false" expression
expansionInfo.nextSourceExpression()
if (isConditionTrue) {
// If the condition is true, we can set the EXCLUSIVE END of this expansion to the position of the
// "false" expression, and then we reset the current index to the position of the "true" expression.
expansionInfo.endExclusive = expansionInfo.i
expansionInfo.i = trueExpressionPosition
}
return expansionInfo.nextSourceExpression()
}
}

private enum class ExpansionKind(val expander: Expander) {
Container(SimpleExpander),
TemplateBody(SimpleExpander),
Expand All @@ -166,6 +209,10 @@ class MacroEvaluator {
MakeString(MakeStringExpander),
MakeSymbol(MakeSymbolExpander),
MakeDecimal(MakeDecimalExpander),
IfNone(IfExpander.IF_NONE),
IfSome(IfExpander.IF_SOME),
IfSingle(IfExpander.IF_SINGLE),
IfMulti(IfExpander.IF_MULTI),
;

companion object {
Expand All @@ -178,6 +225,10 @@ class MacroEvaluator {
SystemMacro.MakeString -> MakeString
SystemMacro.MakeSymbol -> MakeSymbol
SystemMacro.MakeDecimal -> MakeDecimal
SystemMacro.IfNone -> IfNone
SystemMacro.IfSome -> IfSome
SystemMacro.IfSingle -> IfSingle
SystemMacro.IfMulti -> IfMulti
}
}
}
Expand Down
67 changes: 63 additions & 4 deletions src/test/java/com/amazon/ion/impl/macro/MacroEvaluatorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments.arguments
import org.junit.jupiter.params.provider.MethodSource

class MacroEvaluatorTest {

Expand All @@ -35,7 +38,7 @@ class MacroEvaluatorTest {
}
}

val ABCs_MACRO = template() {
val ABCs_LIST_MACRO = template {
list {
string("a")
string("b")
Expand Down Expand Up @@ -186,7 +189,7 @@ class MacroEvaluatorTest {
// [ "a", "b", "c" ]

evaluator.initExpansion {
eexp(ABCs_MACRO) {}
eexp(ABCs_LIST_MACRO) {}
}

assertIsInstance<ListValue>(evaluator.expandNext())
Expand All @@ -209,7 +212,7 @@ class MacroEvaluatorTest {
// [ "a", "b", "c" ]

evaluator.initExpansion {
eexp(ABCs_MACRO) {}
eexp(ABCs_LIST_MACRO) {}
}

assertIsInstance<ListValue>(evaluator.expandNext())
Expand Down Expand Up @@ -786,8 +789,64 @@ class MacroEvaluatorTest {
assertEquals(null, evaluator.expandNext())
}

companion object {
object IfExpanderTestParameters {
val SINGLE_VALUE = template { int(1) }
val MULTI_VALUE_STREAM = template {
popematt marked this conversation as resolved.
Show resolved Hide resolved
macro(Values) {
expressionGroup {
int(3)
int(4)
int(5)
}
}
}

@JvmStatic
fun parameters() = listOf(
arguments(IfNone, None, true),
arguments(IfNone, SINGLE_VALUE, false),
arguments(IfNone, MULTI_VALUE_STREAM, false),

arguments(IfSome, None, false),
arguments(IfSome, SINGLE_VALUE, true),
arguments(IfSome, MULTI_VALUE_STREAM, true),

arguments(IfSingle, None, false),
arguments(IfSingle, SINGLE_VALUE, true),
arguments(IfSingle, MULTI_VALUE_STREAM, false),

arguments(IfMulti, None, false),
arguments(IfMulti, SINGLE_VALUE, false),
arguments(IfMulti, MULTI_VALUE_STREAM, true),
)
}

@ParameterizedTest
@MethodSource("com.amazon.ion.impl.macro.MacroEvaluatorTest\$IfExpanderTestParameters#parameters")
fun `check 'if' expansion logic`(ifSpecialForm: SystemMacro, expressionToTest: Macro, expectMatches: Boolean) {
// Given:
// (macro test_if (x*) (<ifSpecialForm> (%x) "a" "b"))
// When:
// (:test_if <expressionToTest>)
// Then:
// "a" or "b" depending on whether we expect it to match.

val theMacro = template("x*") {
macro(ifSpecialForm) {
variable(0)
string("a")
string("b")
}
}

evaluator.initExpansion { eexp(theMacro) { eexp(expressionToTest) {} } }

val expectedString = if (expectMatches) "a" else "b"
assertEquals(StringValue(value = expectedString), evaluator.expandNext())
assertEquals(null, evaluator.expandNext())
}

companion object {
/** Helper function to create template macros */
fun template(vararg parameters: String, body: TemplateDsl.() -> Unit): Macro {
val signature = parameters.map {
Expand Down
Loading