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

Kotlinize inherited tests #358

Merged
merged 9 commits into from
Jan 15, 2025
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
@@ -0,0 +1,5 @@
package it.krzeminski.snakeyaml.engine.kmp.usecases.inherited

import it.krzeminski.snakeyaml.engine.kmp.exceptions.YamlEngineException

class CanonicalException(message: String) : YamlEngineException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package it.krzeminski.snakeyaml.engine.kmp.usecases.inherited

import it.krzeminski.snakeyaml.engine.kmp.common.Anchor
import it.krzeminski.snakeyaml.engine.kmp.common.FlowStyle
import it.krzeminski.snakeyaml.engine.kmp.common.ScalarStyle
import it.krzeminski.snakeyaml.engine.kmp.common.SpecVersion
import it.krzeminski.snakeyaml.engine.kmp.events.AliasEvent
import it.krzeminski.snakeyaml.engine.kmp.events.DocumentEndEvent
import it.krzeminski.snakeyaml.engine.kmp.events.DocumentStartEvent
import it.krzeminski.snakeyaml.engine.kmp.events.Event
import it.krzeminski.snakeyaml.engine.kmp.events.ImplicitTuple
import it.krzeminski.snakeyaml.engine.kmp.events.MappingEndEvent
import it.krzeminski.snakeyaml.engine.kmp.events.MappingStartEvent
import it.krzeminski.snakeyaml.engine.kmp.events.ScalarEvent
import it.krzeminski.snakeyaml.engine.kmp.events.SequenceEndEvent
import it.krzeminski.snakeyaml.engine.kmp.events.SequenceStartEvent
import it.krzeminski.snakeyaml.engine.kmp.events.StreamEndEvent
import it.krzeminski.snakeyaml.engine.kmp.events.StreamStartEvent
import it.krzeminski.snakeyaml.engine.kmp.nodes.Tag
import it.krzeminski.snakeyaml.engine.kmp.tokens.AliasToken
import it.krzeminski.snakeyaml.engine.kmp.tokens.AnchorToken
import it.krzeminski.snakeyaml.engine.kmp.tokens.ScalarToken
import it.krzeminski.snakeyaml.engine.kmp.tokens.TagToken
import it.krzeminski.snakeyaml.engine.kmp.tokens.Token

class CanonicalParser(data: String, private val label: String) {
private val scanner = CanonicalScanner(data, label)
private val events = mutableListOf<Event>()
private var parsed = false

// stream: STREAM-START document* STREAM-END
private fun parseStream() {
scanner.getToken(Token.ID.StreamStart)
events.add(StreamStartEvent(null, null))
while (!scanner.checkToken(Token.ID.StreamEnd)) {
if (scanner.checkToken(Token.ID.Directive, Token.ID.DocumentStart)) {
parseDocument()
} else {
throw CanonicalException("document is expected, got ${scanner.tokens[0]} in $label")
}
}
scanner.getToken(Token.ID.StreamEnd)
events.add(StreamEndEvent(null, null))
}

// document: DIRECTIVE? DOCUMENT-START node
private fun parseDocument() {
if (scanner.checkToken(Token.ID.Directive)) {
scanner.getToken(Token.ID.Directive)
}
scanner.getToken(Token.ID.DocumentStart)
events.add(DocumentStartEvent(true, SpecVersion(1, 2), emptyMap(), null, null))
parseNode();
if (scanner.checkToken(Token.ID.DocumentEnd)) {
scanner.getToken(Token.ID.DocumentEnd)
}
events.add(DocumentEndEvent(true, null, null))
}

// node: ALIAS | ANCHOR? TAG? (SCALAR|sequence|mapping)
private fun parseNode() {
if (scanner.checkToken(Token.ID.Alias)) {
val token = scanner.next() as AliasToken
events.add(AliasEvent(token.value, null, null))
} else {
var anchor: Anchor? = null
if (scanner.checkToken(Token.ID.Anchor)) {
val token = scanner.next() as AnchorToken
anchor = token.value
}
var tag: String? = null
if (scanner.checkToken(Token.ID.Tag)) {
var token = scanner.next() as TagToken
tag = token.value.handle + token.value.suffix
}
if (scanner.checkToken(Token.ID.Scalar)) {
val token = scanner.next() as ScalarToken
events.add(
ScalarEvent(
anchor, tag, ImplicitTuple(false, false), token.value,
ScalarStyle.PLAIN, null, null
)
)
} else if (scanner.checkToken(Token.ID.FlowSequenceStart)) {
events.add(
SequenceStartEvent(
anchor, Tag.SEQ.value, false,
FlowStyle.AUTO, null, null
)
)
parseSequence();
} else if (scanner.checkToken(Token.ID.FlowMappingStart)) {
events.add(
MappingStartEvent(
anchor, Tag.MAP.value, false,
FlowStyle.AUTO, null, null
)
)
parseMapping();
} else {
throw CanonicalException("SCALAR, '[', or '{' is expected, got ${scanner.tokens[0]}")
}
}
}

// sequence: SEQUENCE-START (node (ENTRY node)*)? ENTRY? SEQUENCE-END
private fun parseSequence() {
scanner.getToken(Token.ID.FlowSequenceStart)
if (!scanner.checkToken(Token.ID.FlowSequenceEnd)) {
parseNode()
while (!scanner.checkToken(Token.ID.FlowSequenceEnd)) {
scanner.getToken(Token.ID.FlowEntry)
if (!scanner.checkToken(Token.ID.FlowSequenceEnd)) {
parseNode()
}
}
}
scanner.getToken(Token.ID.FlowSequenceEnd)
events.add(SequenceEndEvent(null, null))
}

// mapping: MAPPING-START (map_entry (ENTRY map_entry)*)? ENTRY? MAPPING-END
private fun parseMapping() {
scanner.getToken(Token.ID.FlowMappingStart)
if (!scanner.checkToken(Token.ID.FlowMappingEnd)) {
parseMapEntry()
while (!scanner.checkToken(Token.ID.FlowMappingEnd)) {
scanner.getToken(Token.ID.FlowEntry)
if (!scanner.checkToken(Token.ID.FlowMappingEnd)) {
parseMapEntry()
}
}
}
scanner.getToken(Token.ID.FlowMappingEnd)
events.add(MappingEndEvent(null, null))
}

// map_entry: KEY node VALUE node
private fun parseMapEntry() {
scanner.getToken(Token.ID.Key);
parseNode();
scanner.getToken(Token.ID.Value);
parseNode();
}

fun parse() {
parseStream()
parsed = true
}

fun next(): Event {
if (!parsed) {
parse()
}
return events.removeAt(0)
}

/**
* Check the type of the next event.
*/
fun checkEvent(choice: Event.ID): Boolean {
if (!parsed) {
parse()
}
if (!events.isEmpty()) {
return events[0].eventId == choice
}
return false
}

/**
* Get the next event.
*/
fun peekEvent(): Event {
if (!parsed) {
parse()
}
if (events.isEmpty()) {
throw NoSuchElementException("No more Events found.")
} else {
return events[0]
}
}

fun hasNext(): Boolean {
if (!parsed) {
parse()
}
return !events.isEmpty()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import it.krzeminski.snakeyaml.engine.kmp.exceptions.Mark
import it.krzeminski.snakeyaml.engine.kmp.nodes.Tag
import it.krzeminski.snakeyaml.engine.kmp.scanner.Scanner
import it.krzeminski.snakeyaml.engine.kmp.tokens.*
import org.snakeyaml.engine.usecases.inherited.CanonicalException

class CanonicalScanner(data: String, private val label: String) : Scanner {
private val data = data + "\u0000"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package it.krzeminski.snakeyaml.engine.kmp.usecases.inherited

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldNotBeEmpty
import io.kotest.matchers.comparables.shouldBeGreaterThan
import it.krzeminski.snakeyaml.engine.kmp.tokens.Token
import java.io.InputStream
import java.nio.file.Files

class InheritedCanonicalTest: FunSpec({
test("Canonical scan") {
val files = getStreamsByExtension(".canonical")
files.size shouldBeGreaterThan 0
files.forEach { file ->
Files.newInputStream(file.toPath()).use { input ->
val tokens = canonicalScan(input, file.name)
tokens.shouldNotBeEmpty()
}
}
}

test("Canonical parse") {
val files = getStreamsByExtension(".canonical")
files.size shouldBeGreaterThan 0
files.forEach { file ->
Files.newInputStream(file.toPath()).use { input ->
val tokens = canonicalParse(input, file.name)
tokens.shouldNotBeEmpty()
}
}
}
})

private fun canonicalScan(input: InputStream, label: String): List<Token> {
val buffer = buildString {
var ch = input.read()
while (ch != -1) {
append(ch.toChar())
ch = input.read()
}
}
val scanner = CanonicalScanner(buffer.toString().replace(System.lineSeparator(), "\n"), label)
return buildList {
while (scanner.hasNext()) {
add(scanner.next())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package it.krzeminski.snakeyaml.engine.kmp.usecases.inherited

import it.krzeminski.snakeyaml.engine.kmp.api.LoadSettings
import it.krzeminski.snakeyaml.engine.kmp.api.YamlUnicodeReader
import it.krzeminski.snakeyaml.engine.kmp.events.Event
import it.krzeminski.snakeyaml.engine.kmp.parser.ParserImpl
import it.krzeminski.snakeyaml.engine.kmp.scanner.StreamReader
import okio.source
import org.snakeyaml.engine.v2.utils.TestUtils
import java.io.File
import java.io.FilenameFilter
import java.io.InputStream

private const val PATH = "/inherited_yaml_1_1"

fun getResource(theName: String): String =
TestUtils.getResource("$PATH/$theName")

@JvmOverloads
fun getStreamsByExtension(
extension: String,
onlyIfCanonicalPresent: Boolean = false,
): Array<File> =
File("src/jvmTest/resources$PATH").also {
require(it.exists()) { "Folder not found: ${it.absolutePath}" }
require(it.isDirectory)
}.listFiles(InheritedFilenameFilter(extension, onlyIfCanonicalPresent))

fun getFileByName(name: String): File =
File("src/jvmTest/resources$PATH/$name").also {
require(it.exists()) { "Folder not found: ${it.absolutePath}" }
require(it.isFile)
}

fun canonicalParse(input2: InputStream, label: String): List<Event> {
val settings = LoadSettings.builder().setLabel(label).build()
input2.use {
val reader = StreamReader(settings, YamlUnicodeReader(input2.source()))
val buffer = StringBuffer()
while (reader.peek() != 0) {
buffer.appendCodePoint(reader.peek())
reader.forward()
}
val parser = CanonicalParser(buffer.toString().replace(System.lineSeparator(), "\n"), label)
val result = buildList {
while (parser.hasNext()) {
add(parser.next())
}
}
return result
}
}

fun parse(input: InputStream): List<Event> {
val settings = LoadSettings.builder().build()
input.use {
val reader = StreamReader(settings, YamlUnicodeReader(input.source()))
val parser = ParserImpl(settings, reader)
val result = buildList {
while (parser.hasNext()) {
add(parser.next())
}
}
return result
}
}

private class InheritedFilenameFilter(
private val extension: String,
private val onlyIfCanonicalPresent: Boolean,
) : FilenameFilter {
override fun accept(dir: File?, name: String): Boolean {
val position = name.lastIndexOf('.')
val canonicalFileName = name.substring(0, position) + ".canonical"
val canonicalFile = File(dir, canonicalFileName)
return if (onlyIfCanonicalPresent && !canonicalFile.exists()) {
false
} else {
name.endsWith(extension)
}
}
}
Loading
Loading