commonMain.pro.felixo.protobuf.schemadocument.SchemaDocumentReader.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of protobuf-kotlin-schemadocument-jvm Show documentation
Show all versions of protobuf-kotlin-schemadocument-jvm Show documentation
Protocol Buffers 3 support for Kotlin Multiplatform
The newest version!
package pro.felixo.protobuf.schemadocument
import pro.felixo.protobuf.EnumValue
import pro.felixo.protobuf.FieldNumber
import pro.felixo.protobuf.FieldRule
import pro.felixo.protobuf.Identifier
/**
* Reads .proto syntax into [SchemaDocument]s. Note that only a subset of the .proto syntax is supported, and only
* the "proto3" syntax.
*/
class SchemaDocumentReader(private val tokenizer: SchemaDocumentTokenizer = SchemaDocumentTokenizer()) {
fun readSchema(input: String): SchemaDocument {
val tokens = tokenizer.tokenize(input).iterator()
expectSyntaxDeclaration(tokens)
var packageName: Token.Identifier? = null
val types = mutableListOf()
while (tokens.hasNext())
when (val token = tokens.expectToken()) {
Token.Identifier("package") ->
if (packageName == null)
packageName = readPackageName(tokens)
else
error("Duplicate package declaration")
Token.Identifier("message") -> types.add(readMessage(tokens))
Token.Identifier("enum") -> types.add(readEnum(tokens))
else -> error("Unexpected token: $token")
}
return SchemaDocument(types)
}
private fun readPackageName(tokens: Iterator): Token.Identifier {
val packageName = tokens.expect()
tokens.expect(Token.Semicolon)
return packageName
}
private fun expectSyntaxDeclaration(tokens: Iterator) {
tokens.expect(Token.Identifier("syntax"))
tokens.expect(Token.Equals)
tokens.expect(Token.StringLiteral("\"proto3\""))
tokens.expect(Token.Semicolon)
}
private fun expectType(token: Token): FieldType = when (token) {
is Token.Identifier ->
SCALARS.firstOrNull {
it.name == token.text
}
?: FieldType.Reference(token.components.map { Identifier(it) })
else -> error("Expected identifier, but got: $token")
}
private fun readMessage(tokens: Iterator): Message {
val name = Identifier(tokens.expect().text)
tokens.expect()
val members = mutableListOf()
val types = mutableListOf()
val reservedNames = mutableListOf()
val reservedNumbers = mutableListOf()
while (true) {
when (val token = tokens.expectToken()) {
is Token.CloseBrace -> break
Token.Identifier("reserved") -> putReserved(reservedNames, reservedNumbers, tokens)
Token.Identifier("message") -> types.add(readMessage(tokens))
Token.Identifier("enum") -> types.add(readEnum(tokens))
is Token.Identifier -> members.putMember(token, tokens)
else -> error("Unexpected token in message $name: $token")
}
}
return Message(
name,
members,
types,
reservedNames,
reservedNumbers
)
}
private fun readEnum(tokens: Iterator): Enum {
val name = Identifier(tokens.expect().text)
tokens.expect()
val values = mutableListOf()
val reservedNames = mutableListOf()
val reservedNumbers = mutableListOf()
var allowAlias = false
while (true) {
when (val token = tokens.expectToken()) {
is Token.CloseBrace -> break
Token.Identifier("option") -> allowAlias = readAllowAliasOption(tokens)
Token.Identifier("reserved") -> putReserved(reservedNames, reservedNumbers, tokens)
is Token.Identifier -> values.putEnumValue(token, tokens)
else -> error("Unexpected token in enum $name: $token")
}
}
return Enum(
name,
values,
allowAlias,
reservedNames,
reservedNumbers
)
}
private fun readAllowAliasOption(tokens: Iterator): Boolean {
tokens.expect(Token.Identifier("allow_alias"))
tokens.expect(Token.Equals)
return tokens.expectBoolean()
}
private fun Iterator.expectBoolean(): Boolean {
val value = expect()
expect()
return when (val text = value.text) {
"true" -> true
"false" -> false
else -> error("Expected boolean, but got: $text")
}
}
private fun MutableList.putEnumValue(name: Token.Identifier, tokens: Iterator) {
tokens.expect()
val number = tokens.expect()
tokens.expect()
add(EnumValue(Identifier(name.text), number.value))
}
private fun MutableList.putMember(firstToken: Token.Identifier, tokens: Iterator) {
when (firstToken) {
Token.Identifier("oneof") -> add(readOneOf(tokens))
else -> add(readField(firstToken, tokens))
}
}
private fun readOneOf(tokens: Iterator): OneOf {
val name = tokens.expect()
tokens.expect()
val fields = mutableListOf()
while (true)
when (val token = tokens.expectToken()) {
is Token.CloseBrace -> break
is Token.Identifier -> fields.add(readField(token, tokens))
else -> error("Unexpected token in oneof $name: $token")
}
return OneOf(Identifier(name.text), fields)
}
private fun readField(firstToken: Token.Identifier, tokens: Iterator): Field {
val rule = when (firstToken) {
Token.Identifier("optional") -> FieldRule.Optional
Token.Identifier("repeated") -> FieldRule.Repeated
Token.Identifier("singular") -> FieldRule.Singular
Token.Identifier("map") -> error("Map fields are not supported")
else -> null
}
val type = expectType(if (rule != null) tokens.expect() else firstToken)
val name = tokens.expect()
tokens.expect()
val number = tokens.expect()
tokens.expect()
return Field(
Identifier(name.text),
type,
FieldNumber(number.value),
rule ?: FieldRule.Singular
)
}
private fun putReserved(
reservedNames: MutableList,
reservedNumbers: MutableList,
tokens: Iterator
) {
do {
val more = when (val token = tokens.expectToken()) {
is Token.StringLiteral -> putReservedName(reservedNames, token, tokens)
is Token.NumberLiteral -> putReservedNumber(tokens, reservedNumbers, token)
else -> error("Unexpected token in reserved: $token")
}
} while (more)
}
private fun putReservedName(
reservedNames: MutableList,
token: Token.StringLiteral,
tokens: Iterator
): Boolean {
reservedNames.add(Identifier(token.value))
return when (val secondToken = tokens.expectToken()) {
is Token.Comma -> true
is Token.Semicolon -> false
else -> error("Unexpected token in reserved: $secondToken")
}
}
private fun putReservedNumber(
tokens: Iterator,
reservedNumbers: MutableList,
token: Token.NumberLiteral
): Boolean = when (val secondToken = tokens.expectToken()) {
is Token.Comma -> {
reservedNumbers.add(token.value..token.value)
true
}
is Token.Semicolon -> {
reservedNumbers.add(token.value..token.value)
false
}
Token.Identifier("to") -> {
when (val upperBound = tokens.expectToken()) {
is Token.NumberLiteral -> reservedNumbers.add(token.value..upperBound.value)
Token.Identifier("max") -> reservedNumbers.add(token.value..Int.MAX_VALUE)
else -> error("Unexpected token in reserved: $upperBound")
}
when (val terminator = tokens.expectToken()) {
is Token.Comma -> true
is Token.Semicolon -> false
else -> error("Unexpected token in reserved: $terminator")
}
}
else -> error("Unexpected token in reserved: $secondToken")
}
private fun Iterator.expect(expected: Token): Token =
expectToken().let { if (it == expected) it else error("Expected $expected, but got: $it") }
private inline fun Iterator.expect(): T =
expectToken().let { if (it is T) it else error("Expected ${T::class.simpleName}, but got: $it") }
private fun Iterator.expectToken() = if (hasNext()) next() else error("Unexpected end of input")
}