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

Add support for parsing and modeling WebIDL dictionaries #3

Merged
merged 6 commits into from
Feb 16, 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
47 changes: 47 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ class IdlAttribute private constructor(builder: Builder) : IdlDecoratedElement(b

fun build() = IdlAttribute(this)
}

}
Original file line number Diff line number Diff line change
@@ -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<IdlMember.Builder>()
val superDictionaries = mutableSetOf<String>()
var sourcePackage = ""

fun addAttribute(attribute: IdlMember.Builder) { members += attribute }
fun build() = IdlDictionary(this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,14 @@ class IdlInterface(builder: Builder) : IdlDecoratedElement(builder) {
val superInterfaces = mutableSetOf<String>()
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)
}
}
35 changes: 35 additions & 0 deletions webidl-util/src/main/kotlin/de/fabmax/webidl/model/IdlMember.kt
Original file line number Diff line number Diff line change
@@ -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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import java.util.*

class IdlModel private constructor(builder: Builder) : IdlElement(builder) {
val interfaces: List<IdlInterface>
val dictionaries: List<IdlDictionary>
val enums: List<IdlEnum>

val interfacesByName: Map<String, IdlInterface>

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) }
}
Expand Down Expand Up @@ -57,10 +60,12 @@ class IdlModel private constructor(builder: Builder) : IdlElement(builder) {

class Builder : IdlElement.Builder("root") {
val interfaces = mutableListOf<IdlInterface.Builder>()
val dictionaries = mutableListOf<IdlDictionary.Builder>()
val implements = mutableListOf<Pair<String, String>>()
val enums = mutableListOf<IdlEnum.Builder>()

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 }

Expand Down
Original file line number Diff line number Diff line change
@@ -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<RootParser>().builder.addDictionary(builder)

parseChildren("}")

popToken(";")
parserState.popParser()
return interfaceName.second
}
}
Original file line number Diff line number Diff line change
@@ -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<DictionaryParser>().builder.addAttribute(builder)

parserState.popParser()
return tokens.second
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<WebIdlParserType> = 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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) })

Expand Down
8 changes: 8 additions & 0 deletions webidl-util/src/test/kotlin/ParserTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion webidl-util/src/test/resources/test.idl
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,10 @@ interface JavaErrorCallback {
void JavaErrorCallback();
void reportError(ErrorCode code, DOMString message);
AnotherInterface someOtherMethod(AnInterface anInterface);
};
};

dictionary AnDictionary {
unsigned long long someMember;
required unsigned long long requiredMember;
USVString someMemberWithDefaultValue = "my string";
};