From 34ea27796739f5390898038ac617437cb10f636a Mon Sep 17 00:00:00 2001 From: ygdrasil-io Date: Wed, 19 Feb 2025 00:12:31 +0100 Subject: [PATCH 1/2] Add support for `setlike` in WebIDL parsing This commit introduces parsing and model representation for `setlike` attributes in WebIDL. It includes updates to the parser, new test cases, and necessary changes to the IDL model to handle `setlike` declarations and their types. --- .../de/fabmax/webidl/model/IdlInterface.kt | 2 ++ .../de/fabmax/webidl/model/IdlSetLike.kt | 25 +++++++++++++++++++ .../de/fabmax/webidl/parser/SetLikeParser.kt | 23 +++++++++++++++++ .../fabmax/webidl/parser/WebIdlParserType.kt | 12 ++++++++- webidl-util/src/test/kotlin/ParserTest.kt | 7 +++++- webidl-util/src/test/resources/test.idl | 5 ++++ 6 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlSetLike.kt create mode 100644 webidl-util/src/main/kotlin/de/fabmax/webidl/parser/SetLikeParser.kt diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlInterface.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlInterface.kt index 5e74e49..800ae8b 100644 --- a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlInterface.kt +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlInterface.kt @@ -7,6 +7,7 @@ class IdlInterface(builder: Builder) : IdlDecoratedElement(builder) { val superInterfaces = builder.superInterfaces.toList() val sourcePackage = builder.sourcePackage val isMixin = builder.isMixin + val setLike = builder.setLike?.build() init { functions = builder.functions.flatMap { it.build() } @@ -43,6 +44,7 @@ class IdlInterface(builder: Builder) : IdlDecoratedElement(builder) { val superInterfaces = mutableSetOf() var sourcePackage = "" var isMixin = false + var setLike: IdlSetLike.Builder? = null fun addAttribute(attribute: IdlAttribute.Builder) { attributes += attribute diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlSetLike.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlSetLike.kt new file mode 100644 index 0000000..c00731f --- /dev/null +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlSetLike.kt @@ -0,0 +1,25 @@ +package de.fabmax.webidl.model + +class IdlSetLike private constructor(builder: Builder) : IdlElement(builder) { + val type = builder.type + + var parentInterface: IdlInterface? = null + private set + + fun finishModel(parentInterface: IdlInterface) { + this.parentModel = parentInterface.parentModel + this.parentInterface = parentInterface + } + + override fun toString(indent: String): String { + val str = StringBuilder(indent) + str.append("readonly setlike<$type>").append(";") + return str.toString() + } + + class Builder(var type: IdlType) : IdlElement.Builder("setlike") { + + fun build() = IdlSetLike(this) + } + +} \ No newline at end of file diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/SetLikeParser.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/SetLikeParser.kt new file mode 100644 index 0000000..60c5394 --- /dev/null +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/SetLikeParser.kt @@ -0,0 +1,23 @@ +package de.fabmax.webidl.parser + + +import de.fabmax.webidl.model.IdlSetLike +import de.fabmax.webidl.model.IdlType + +class SetLikeParser(parserState: WebIdlParser.ParserState) : ElementParser(parserState, + WebIdlParserType.Function +) { + lateinit var builder: IdlSetLike.Builder + + override suspend fun parse(): String { + val type = (popUntilPattern(";")?.first ?: parserException("Failed parsing setlike")) + .substringAfter("<") + .substringBefore(">") + .let { IdlType(it, false) } + + builder = IdlSetLike.Builder(type) + parserState.parentParser().builder.setLike = builder + parserState.popParser() + return builder.name + } +} \ No newline at end of file diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/WebIdlParserType.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/WebIdlParserType.kt index e8169c4..b8331b1 100644 --- a/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/WebIdlParserType.kt +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/WebIdlParserType.kt @@ -37,7 +37,7 @@ enum class WebIdlParserType { }, Interface { - override fun possibleChildren() = listOf(Decorators, LineComment, BlockComment, Attribute, Function) + override fun possibleChildren() = listOf(Decorators, LineComment, BlockComment, Attribute, Function, SetLike) override suspend fun matches(stream: WebIdlStream) = stream.startsWith("interface") override fun newParser(parserState: WebIdlParser.ParserState) = parserState.pushParser( InterfaceParser( @@ -46,6 +46,16 @@ enum class WebIdlParserType { ) }, + SetLike { + override fun possibleChildren(): List = emptyList() + override suspend fun matches(stream: WebIdlStream) = stream.startsWith("readonly setlike") + override fun newParser(parserState: WebIdlParser.ParserState) = parserState.pushParser( + SetLikeParser( + parserState + ) + ) + }, + Attribute { override fun possibleChildren(): List = emptyList() override suspend fun matches(stream: WebIdlStream) = stream.startsWith("attribute") diff --git a/webidl-util/src/test/kotlin/ParserTest.kt b/webidl-util/src/test/kotlin/ParserTest.kt index 22a1fca..3bec3d1 100644 --- a/webidl-util/src/test/kotlin/ParserTest.kt +++ b/webidl-util/src/test/kotlin/ParserTest.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test +import kotlin.test.assertNotNull class ParserTest { @@ -22,7 +23,7 @@ class ParserTest { } assertTrue(model.dictionaries.size == 1) - assertTrue(model.interfaces.size == 5) + assertTrue(model.interfaces.size == 6) assertEquals("AnDictionary", model.dictionaries[0].name) @@ -68,6 +69,10 @@ class ParserTest { assertEquals("JavaErrorCallback", model.interfaces[javaErrorCallbackIndex].name) assertEquals("ErrorCallback", model.interfaces[javaErrorCallbackIndex].getDecoratorValue("JSImplementation", "")) + val setLikeIndex = 5 + assertEquals("SetLikeInterface", model.interfaces[setLikeIndex].name) + assertNotNull(model.interfaces[setLikeIndex].setLike) + assertEquals("DOMString", model.interfaces[setLikeIndex].setLike?.type?.typeName) } @Test(expected = ParserException::class) diff --git a/webidl-util/src/test/resources/test.idl b/webidl-util/src/test/resources/test.idl index 82e7d5b..c08473e 100644 --- a/webidl-util/src/test/resources/test.idl +++ b/webidl-util/src/test/resources/test.idl @@ -50,3 +50,8 @@ dictionary AnDictionary { required unsigned long long requiredMember; USVString someMemberWithDefaultValue = "my string"; }; + + +interface SetLikeInterface { + readonly setlike; +}; \ No newline at end of file From 1eef2a874972553efda6091fd6b79ffd3b9e5d5a Mon Sep 17 00:00:00 2001 From: ygdrasil-io Date: Wed, 19 Feb 2025 00:18:47 +0100 Subject: [PATCH 2/2] Add validation for mutually exclusive setlike in IDL interfaces Enforced rules to ensure `setlike` cannot coexist with functions or attributes in `IdlInterface`. Added a test case to verify that invalid `setlike` declarations throw a `ParserException`. This improves the parser's robustness and compliance with specification rules. --- .../kotlin/de/fabmax/webidl/model/IdlInterface.kt | 12 ++++++++---- webidl-util/src/test/kotlin/ParserTest.kt | 6 ++++++ webidl-util/src/test/resources/bad-setlike.idl | 5 +++++ webidl-util/src/test/resources/test.idl | 1 - 4 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 webidl-util/src/test/resources/bad-setlike.idl diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlInterface.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlInterface.kt index 800ae8b..b5a22e0 100644 --- a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlInterface.kt +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlInterface.kt @@ -1,17 +1,21 @@ package de.fabmax.webidl.model +import de.fabmax.webidl.parser.ParserException + class IdlInterface(builder: Builder) : IdlDecoratedElement(builder) { val attributes = List(builder.attributes.size) { builder.attributes[it].build() } - val functions: List - val functionsByName: Map + val functions: List = builder.functions.flatMap { it.build() } + val functionsByName: Map = functions.associateBy { it.name } val superInterfaces = builder.superInterfaces.toList() val sourcePackage = builder.sourcePackage val isMixin = builder.isMixin val setLike = builder.setLike?.build() init { - functions = builder.functions.flatMap { it.build() } - functionsByName = functions.associateBy { it.name } + if (setLike != null) { + if (functions.isNotEmpty()) throw ParserException("functions and setlike are mutually exclusive") + if (attributes.isNotEmpty()) throw ParserException("attributes and setlike are mutually exclusive") + } } fun finishModel(parentModel: IdlModel) { diff --git a/webidl-util/src/test/kotlin/ParserTest.kt b/webidl-util/src/test/kotlin/ParserTest.kt index 3bec3d1..a22efc9 100644 --- a/webidl-util/src/test/kotlin/ParserTest.kt +++ b/webidl-util/src/test/kotlin/ParserTest.kt @@ -87,6 +87,12 @@ class ParserTest { WebIdlParser.parseFromInputStream(inStream) } + @Test(expected = ParserException::class) + fun parserTestBadSetLike() { + val inStream = ParserTest::class.java.classLoader.getResourceAsStream("bad-setlike.idl")!! + WebIdlParser.parseFromInputStream(inStream) + } + @Test fun generatorJsTest() { val inStream = ParserTest::class.java.classLoader.getResourceAsStream("test.idl")!! diff --git a/webidl-util/src/test/resources/bad-setlike.idl b/webidl-util/src/test/resources/bad-setlike.idl new file mode 100644 index 0000000..af3b2d1 --- /dev/null +++ b/webidl-util/src/test/resources/bad-setlike.idl @@ -0,0 +1,5 @@ + +interface SetLikeInterface { + readonly setlike; + void JavaErrorCallback(); +}; \ No newline at end of file diff --git a/webidl-util/src/test/resources/test.idl b/webidl-util/src/test/resources/test.idl index c08473e..52e0d3f 100644 --- a/webidl-util/src/test/resources/test.idl +++ b/webidl-util/src/test/resources/test.idl @@ -51,7 +51,6 @@ dictionary AnDictionary { USVString someMemberWithDefaultValue = "my string"; }; - interface SetLikeInterface { readonly setlike; }; \ No newline at end of file