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

Updates 'each' clauses to allow alternating name-strings and fragments #911

Merged
merged 1 commit into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ object ConformanceTestDslInterpreterTest {
(text " { ")
(text " [ ")
(text " ( ")
"invalid timestamp"
(text "2022-99-99T")
(signals "something bad")))
""" to 3,
""" to 4,
"""
(ion_1_x "a test using 'ion_1_x' to create more than one test case"
(text " 1 ")
Expand Down
10 changes: 9 additions & 1 deletion src/test/java/com/amazon/ion/conformance/fragments.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,22 @@ import com.amazon.ionelement.api.ionInt
import com.amazon.ionelement.api.ionSexpOf
import com.amazon.ionelement.api.ionSymbol
import java.io.ByteArrayOutputStream
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract

/** Helper function for creating ivm fragments for the `ion_1_*` keywords */
fun ivm(sexp: SeqElement, major: Int, minor: Int): SeqElement {
return ionSexpOf(listOf(ionSymbol("ivm"), ionInt(major.toLong()), ionInt(minor.toLong())), metas = sexp.metas)
}

@OptIn(ExperimentalContracts::class)
fun AnyElement.isFragment(): Boolean {
contract { returns(true) implies (this@isFragment is SeqElement) }
return this is SeqElement && this.head in FRAGMENT_KEYWORDS
}

// All known fragment keywords
val FRAGMENT_KEYWORDS = setOf("ivm", "text", "bytes", "toplevel", "encoding", "mactab")
private val FRAGMENT_KEYWORDS = setOf("ivm", "text", "bytes", "toplevel", "encoding", "mactab")
// Insert this between every fragment when transcoding to text
val SERIALIZED_TEXT_FRAGMENT_SEPARATOR = "\n".toByteArray(Charsets.UTF_8)

Expand Down
39 changes: 24 additions & 15 deletions src/test/java/com/amazon/ion/conformance/structure.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,19 @@ fun ConformanceTestBuilder.readTest(element: AnyElement): DynamicNode {
return when (sexp.head) {
"document" ->
parserState.readDescription()
.readFragments { updateState { plusFragments(it) } }
.readFragmentSequence()
.readContinuation()

"ion_1_0" ->
parserState.updateState { plusFragment(ivm(sexp, 1, 0)) }
.readDescription()
.readFragments { updateState { plusFragments(it) } }
.readFragmentSequence()
.readContinuation()

"ion_1_1" ->
parserState.updateState { plusFragment(ivm(sexp, 1, 1)) }
.readDescription()
.readFragments { updateState { plusFragments(it) } }
.readFragmentSequence()
.readContinuation()

"ion_1_x" -> {
Expand All @@ -83,10 +83,10 @@ fun ConformanceTestBuilder.readTest(element: AnyElement): DynamicNode {
val ion11Branch = p.updateState { plus("In Ion 1.1", ivm(sexp, 1, 1)) }
p.builder.buildContainer(
ion10Branch
.readFragments { updateState { plusFragments(it) } }
.readFragmentSequence()
.readContinuation(),
ion11Branch
.readFragments { updateState { plusFragments(it) } }
.readFragmentSequence()
.readContinuation(),
)
}
Expand All @@ -100,10 +100,10 @@ fun ConformanceTestBuilder.readTest(element: AnyElement): DynamicNode {
* given in [ParserState]. Returns a [ParserState] with an updated position
* and a list of any fragment expressions that were found.
*/
private fun <T> ParserState.readFragments(useFragments: ParserState.(List<SeqElement>) -> T): T {
private fun ParserState.readFragmentSequence(): ParserState {
val fragments = sexp.tailFrom(pos)
.takeWhile { it is SeqElement && it.head in FRAGMENT_KEYWORDS } as List<SeqElement>
return this.updateState(pos = pos + fragments.size).useFragments(fragments)
.takeWhile { it.isFragment() } as List<SeqElement>
return this.updateState(pos = pos + fragments.size) { plusFragments(fragments) }
}

/**
Expand All @@ -124,20 +124,29 @@ private fun ParserState.readDescription(): ParserState {
/** Reads a `then` clause, starting _after_ the `then` keyword. */
private fun ParserState.readThen(): List<DynamicNode> {
return readDescription()
.readFragments { frags -> updateState { plusFragments(frags) } }
.readFragmentSequence()
.readContinuation()
.let(::listOf)
}

/** Reads an `each` clause, starting _after_ the `each` keyword. */
private fun ParserState.readEach(): List<DynamicNode> {
// TODO: Handle case where 0 fragments
return readDescription()
.readFragments {
it.mapIndexed { i, frag ->
updateState { plus(name = "[$i]", frag) }.readContinuation()
}
// TODO: Handle case where 0 fragments, if such a case ever gets introduced to the conformance suite
var currentDescription = "«${sexp.head}»"
var continuationPosition = pos
val nameFragmentPairs = mutableListOf<Pair<String, SeqElement>>()
for (element in sexp.tailFrom(pos)) {
when {
element is StringElement -> currentDescription = element.textValueOrNull ?: currentDescription
element.isFragment() -> nameFragmentPairs.add(currentDescription to element)
else -> break
}
continuationPosition++
}

return nameFragmentPairs.mapIndexed { i, (name, frag) ->
updateState(continuationPosition) { plus(name = "$name [$i]", frag) }.readContinuation()
}
}

/** Reads an extension, returning a list of test case nodes constructed from those extensions. */
Expand Down
Loading