diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ec327f3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,47 @@ +name: Tests + +on: + push: + branches: [ main, 'release/**', 'feature/**', 'fix/**' ] + paths-ignore: + - '*.md' + - '*.yml' + - '.github/workflows/**' + pull_request: + branches: [ main, 'release/**', 'feature/**', 'fix/**' ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + tests: + strategy: + fail-fast: false + matrix: + # add macos-latest when upgrading to JDK 17 + os: [ ubuntu-latest, windows-latest ] + runs-on: ${{ matrix.os }} + env: + CI: "true" + steps: + - uses: actions/checkout@v4 + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 8 + cache: 'gradle' + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: ${{ runner.os }}-gradle + - name: assemble + run: ./gradlew assemble + - name: build + run: ./gradlew build + - name: check + run: ./gradlew check diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlAttribute.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlAttribute.kt index 90b5ed0..fcfcbcd 100644 --- a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlAttribute.kt +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlAttribute.kt @@ -32,4 +32,5 @@ class IdlAttribute private constructor(builder: Builder) : IdlDecoratedElement(b fun build() = IdlAttribute(this) } + } \ No newline at end of file diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlDictionary.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlDictionary.kt new file mode 100644 index 0000000..f405e8c --- /dev/null +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlDictionary.kt @@ -0,0 +1,36 @@ +package de.fabmax.webidl.model + +class IdlDictionary(builder: Builder) : IdlDecoratedElement(builder) { + val members = List(builder.members.size) { builder.members[it].build() } + val superDictionaries = builder.superDictionaries.toList() + val sourcePackage = builder.sourcePackage + + fun finishModel(parentModel: IdlModel) { + this.parentModel = parentModel + members.forEach { it.finishModel(this) } + } + + override fun toString(indent: String): String { + val subIndent = "$indent " + val str = StringBuilder() + str.append(decoratorsToStringOrEmpty(indent, "\n")) + str.append("${indent}dictionary $name { ") + + if (members.isNotEmpty()) { + str.append("\n") + str.append(members.joinToString("\n", postfix = "\n", transform = { it.toString(subIndent) })) + } + str.append("$indent};") + superDictionaries.forEach { str.append("\n$indent$name implements $it;") } + return str.toString() + } + + class Builder(name: String) : IdlDecoratedElement.Builder(name) { + val members = mutableListOf() + val superDictionaries = mutableSetOf() + var sourcePackage = "" + + fun addAttribute(attribute: IdlMember.Builder) { members += attribute } + fun build() = IdlDictionary(this) + } +} \ No newline at end of file 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 9770e89..961146a 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 @@ -42,8 +42,14 @@ class IdlInterface(builder: Builder) : IdlDecoratedElement(builder) { val superInterfaces = mutableSetOf() var sourcePackage = "" - fun addAttribute(attribute: IdlAttribute.Builder) { attributes += attribute } - fun addFunction(function: IdlFunction.Builder) { functions += function } + fun addAttribute(attribute: IdlAttribute.Builder) { + attributes += attribute + } + + fun addFunction(function: IdlFunction.Builder) { + functions += function + } + fun build() = IdlInterface(this) } } \ No newline at end of file diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlMember.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlMember.kt new file mode 100644 index 0000000..69cf763 --- /dev/null +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlMember.kt @@ -0,0 +1,35 @@ +package de.fabmax.webidl.model + +class IdlMember private constructor(builder: Builder) : IdlDecoratedElement(builder) { + val type = builder.type + val isRequired = builder.isRequired + var defaultValue: String? = builder.defaultValue + + var parentDictionary: IdlDictionary? = null + private set + + fun finishModel(parentDictionary: IdlDictionary?) { + this.parentDictionary = parentDictionary + this.parentModel = parentDictionary?.parentModel ?: error("Parent model missing") + } + + override fun toString(indent: String): String { + val str = StringBuilder(indent) + str.append(decoratorsToStringOrEmpty(postfix = " ")) + if (isRequired) { + str.append("required ") + } + str.append(type).append(" ").append(name) + if (defaultValue != null) { str.append(" = $defaultValue")} + str.append(";") + return str.toString() + } + + class Builder(name: String, var type: IdlType) : IdlDecoratedElement.Builder(name) { + var isRequired = false + var defaultValue: String? = null + + fun build() = IdlMember(this) + } + +} \ No newline at end of file diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlModel.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlModel.kt index 485ce0f..24405bc 100644 --- a/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlModel.kt +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlModel.kt @@ -4,15 +4,18 @@ import java.util.* class IdlModel private constructor(builder: Builder) : IdlElement(builder) { val interfaces: List + val dictionaries: List val enums: List val interfacesByName: Map init { interfaces = List(builder.interfaces.size) { builder.interfaces[it].build() } + dictionaries = List(builder.dictionaries.size) { builder.dictionaries[it].build() } interfacesByName = interfaces.associateBy { it.name } enums = List(builder.enums.size) { builder.enums[it].build() } + dictionaries.forEach { it.finishModel(this) } interfaces.forEach { it.finishModel(this) } enums.forEach { it.finishModel(this) } } @@ -57,10 +60,12 @@ class IdlModel private constructor(builder: Builder) : IdlElement(builder) { class Builder : IdlElement.Builder("root") { val interfaces = mutableListOf() + val dictionaries = mutableListOf() val implements = mutableListOf>() val enums = mutableListOf() fun addInterface(idlInterface: IdlInterface.Builder) { interfaces += idlInterface } + fun addDictionary(idlDictionary: IdlDictionary.Builder) { dictionaries += idlDictionary } fun addImplements(concreteInterface: String, superInterface: String) { implements += concreteInterface to superInterface } fun addEnum(idlEnum: IdlEnum.Builder) { enums += idlEnum } diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/DictionaryParser.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/DictionaryParser.kt new file mode 100644 index 0000000..07a6a91 --- /dev/null +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/DictionaryParser.kt @@ -0,0 +1,25 @@ +package de.fabmax.webidl.parser + +import de.fabmax.webidl.model.IdlDictionary + +class DictionaryParser(parserState: WebIdlParser.ParserState) : ElementParser(parserState, + WebIdlParserType.Dictionary +) { + lateinit var builder: IdlDictionary.Builder + + override suspend fun parse(): String { + popToken("dictionary") + + val interfaceName = popUntilPattern("\\{") ?: parserException("Failed parsing dictionary name") + builder = IdlDictionary.Builder(interfaceName.first) + builder.sourcePackage = parserState.sourcePackage + parserState.popDecorators(builder) + parserState.parentParser().builder.addDictionary(builder) + + parseChildren("}") + + popToken(";") + parserState.popParser() + return interfaceName.second + } +} \ No newline at end of file diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/MemberParser.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/MemberParser.kt new file mode 100644 index 0000000..1cba34c --- /dev/null +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/MemberParser.kt @@ -0,0 +1,27 @@ +package de.fabmax.webidl.parser + +import de.fabmax.webidl.model.IdlMember + +class MemberParser(parserState: WebIdlParser.ParserState) : ElementParser(parserState, + WebIdlParserType.Dictionary +) { + lateinit var builder: IdlMember.Builder + + override suspend fun parse(): String { + val isRequired = popIfPresent("required") + val type = parseType() + val tokens = popUntilPattern(";") ?: parserException("Failed parsing member") + builder = if (tokens.first.contains("=")) { + val name = tokens.first.substringBefore("=").trim() + IdlMember.Builder(name, type).also { + it.defaultValue = tokens.first.substringAfter("=").trim() + } + } else IdlMember.Builder(tokens.first, type) + builder.isRequired = isRequired + parserState.popDecorators(builder) + parserState.parentParser().builder.addAttribute(builder) + + parserState.popParser() + return tokens.second + } +} \ 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 1dabc2a..3edf982 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 @@ -4,11 +4,38 @@ import de.fabmax.webidl.model.IdlType enum class WebIdlParserType { Root { - override fun possibleChildren() = listOf(Interface, Enum, LineComment, BlockComment, Decorators, Implements) + override fun possibleChildren() = listOf(Interface, Enum, LineComment, BlockComment, Decorators, Implements, Dictionary) override suspend fun matches(stream: WebIdlStream) = false override fun newParser(parserState: WebIdlParser.ParserState) = RootParser(parserState) }, + Dictionary { + override fun possibleChildren() = listOf(Decorators, LineComment, BlockComment, Member) + override suspend fun matches(stream: WebIdlStream) = stream.startsWith("dictionary") + override fun newParser(parserState: WebIdlParser.ParserState) = parserState.pushParser( + DictionaryParser( + parserState + ) + ) + }, + + Member { + override fun possibleChildren(): List = emptyList() + override suspend fun matches(stream: WebIdlStream): Boolean { + var line = stream.pollUntilPattern(";")?.first ?: return false + if (line.startsWith("required")) { + line = line.substring("required".length + 1) + } + return IdlType.startsWithType(line) + } + + override fun newParser(parserState: WebIdlParser.ParserState) = parserState.pushParser( + MemberParser( + parserState + ) + ) + }, + Interface { override fun possibleChildren() = listOf(Decorators, LineComment, BlockComment, Attribute, Function) override suspend fun matches(stream: WebIdlStream) = stream.startsWith("interface") diff --git a/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/WebIdlStream.kt b/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/WebIdlStream.kt index 0b25eaf..1876bb4 100644 --- a/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/WebIdlStream.kt +++ b/webidl-util/src/main/kotlin/de/fabmax/webidl/parser/WebIdlStream.kt @@ -47,6 +47,11 @@ class WebIdlStream { return buffer.startsWith(prefix) } + suspend fun endWith(prefix: String): Boolean { + readCharacters(prefix.length) + return buffer.endsWith(prefix) + } + suspend fun pollUntilPattern(searchPattern: String, abortPattern: String? = null) = pollUntilPattern(Regex(searchPattern), abortPattern?.let { Regex(it) }) diff --git a/webidl-util/src/test/kotlin/ParserTest.kt b/webidl-util/src/test/kotlin/ParserTest.kt index d78a828..275b235 100644 --- a/webidl-util/src/test/kotlin/ParserTest.kt +++ b/webidl-util/src/test/kotlin/ParserTest.kt @@ -21,8 +21,16 @@ class ParserTest { parser.finish() } + assertTrue(model.dictionaries.size == 1) assertTrue(model.interfaces.size == 4) + + assertEquals("AnDictionary", model.dictionaries[0].name) + assertEquals("someMember", model.dictionaries[0].members[0].name) + assertTrue("requiredMember", model.dictionaries[0].members[1].isRequired) + assertEquals("someMemberWithDefaultValue", model.dictionaries[0].members[2].name) + assertEquals("\"my string\"", model.dictionaries[0].members[2].defaultValue) + assertEquals("AnInterface", model.interfaces[0].name) assertTrue(model.interfaces[0].functions.size == 1) assertEquals("aFunction", model.interfaces[0].functions[0].name) diff --git a/webidl-util/src/test/resources/test.idl b/webidl-util/src/test/resources/test.idl index f60656a..7d2e094 100644 --- a/webidl-util/src/test/resources/test.idl +++ b/webidl-util/src/test/resources/test.idl @@ -32,4 +32,10 @@ interface JavaErrorCallback { void JavaErrorCallback(); void reportError(ErrorCode code, DOMString message); AnotherInterface someOtherMethod(AnInterface anInterface); -}; \ No newline at end of file +}; + +dictionary AnDictionary { + unsigned long long someMember; + required unsigned long long requiredMember; + USVString someMemberWithDefaultValue = "my string"; +};