Convert thrift parser to Kotlin (#175)

This commit is contained in:
Ben Bader 2018-06-10 16:16:06 -07:00 коммит произвёл GitHub
Родитель c2f4a2ede2
Коммит 7dd5f8f77e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
42 изменённых файлов: 3475 добавлений и 3464 удалений

Просмотреть файл

@ -69,6 +69,7 @@ buildscript {
dependencies {
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.14'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.41"
classpath 'org.jetbrains.dokka:dokka-gradle-plugin:0.9.17'
}
}
@ -102,22 +103,6 @@ subprojects { sp ->
options.incremental = true
}
task javadocJar(type: Jar, dependsOn: javadoc) {
from 'build/docs/javadoc'
classifier = 'javadoc'
}
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
classifier = 'sources'
}
artifacts {
archives jar
archives javadocJar
archives sourcesJar
}
test {
testLogging {
events "failed"
@ -139,10 +124,10 @@ task codeCoverageReport(type: JacocoReport) {
}
reports {
xml.enabled true
xml.enabled = true
xml.destination file("$buildDir/reports/jacoco/report.xml")
html.enabled true
csv.enabled false
html.enabled = true
csv.enabled = false
}
afterEvaluate {

Просмотреть файл

@ -82,4 +82,44 @@ afterEvaluate { project ->
required { isRelease && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
if (project.plugins.hasPlugin('org.jetbrains.kotlin') || project.plugins.hasPlugin('kotlin')) {
apply plugin: 'org.jetbrains.dokka'
dokka {
includes = ['Module.md']
linkMapping {
dir = project.file("src/main/kotlin")
url = "https://github.com/Microsoft/thrifty/tree/master/${project.name}/src/main/kotlin"
suffix = "#L"
}
outputFormat = 'html'
outputDirectory = "${project.buildDir}/docs/kdoc"
}
}
task javadocJar(type: Jar) {
if (project.plugins.hasPlugin('org.jetbrains.dokka')) {
dependsOn dokka
from dokka.outputDirectory
} else {
dependsOn javadoc
from 'build/docs/javadoc'
}
classifier = 'javadoc'
}
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
classifier = 'sources'
}
artifacts {
archives jar
archives javadocJar
archives sourcesJar
}
}

Просмотреть файл

@ -65,9 +65,8 @@ internal class ConstantBuilder(
initializer.addStatement("\$L = \$L", name, item)
}
@Suppress("UNCHECKED_CAST")
override fun visitList(listType: ListType) {
val list = value.value() as List<ConstValueElement>
val list = value.getAsList()
val elementType = listType.elementType().trueType
val elementTypeName = typeResolver.getJavaClass(elementType)
val genericName = ParameterizedTypeName.get(TypeNames.LIST, elementTypeName)
@ -75,9 +74,8 @@ internal class ConstantBuilder(
generateSingleElementCollection(elementType, genericName, listImplName, list)
}
@Suppress("UNCHECKED_CAST")
override fun visitSet(setType: SetType) {
val set = value.value() as List<ConstValueElement>
val set = value.getAsList()
val elementType = setType.elementType().trueType
val elementTypeName = typeResolver.getJavaClass(elementType)
val genericName = ParameterizedTypeName.get(TypeNames.SET, elementTypeName)
@ -103,9 +101,8 @@ internal class ConstantBuilder(
}
}
@Suppress("UNCHECKED_CAST")
override fun visitMap(mapType: MapType) {
val map = value.value() as Map<ConstValueElement, ConstValueElement>
val map = value.getAsMap()
val keyType = mapType.keyType().trueType
val valueType = mapType.valueType().trueType
@ -167,22 +164,22 @@ internal class ConstantBuilder(
private fun getNumberLiteral(element: ConstValueElement): Any {
if (!element.isInt) {
throw AssertionError("Expected an int or double, got: " + element.kind())
throw AssertionError("Expected an int or double, got: " + element.kind)
}
return if (element.thriftText().startsWith("0x") || element.thriftText().startsWith("0X")) {
element.thriftText()
return if (element.thriftText.startsWith("0x") || element.thriftText.startsWith("0X")) {
element.thriftText
} else {
element.asInt
element.getAsInt()
}
}
override fun visitBool(boolType: BuiltinType): CodeBlock {
val name: String
if (value.isIdentifier && ("true" == value.asString || "false" == value.asString)) {
name = if ("true" == value.value()) "true" else "false"
if (value.isIdentifier && ("true" == value.getAsString() || "false" == value.getAsString())) {
name = if ("true" == value.getAsString()) "true" else "false"
} else if (value.isInt) {
name = if (value.value() as Long == 0L) "false" else "true"
name = if (value.getAsLong() == 0L) "false" else "true"
} else {
return constantOrError("Invalid boolean constant")
}
@ -224,7 +221,7 @@ internal class ConstantBuilder(
override fun visitDouble(doubleType: BuiltinType): CodeBlock {
return if (value.isInt || value.isDouble) {
CodeBlock.builder().add("(double) \$L", value.asDouble).build()
CodeBlock.builder().add("(double) \$L", value.getAsDouble()).build()
} else {
constantOrError("Invalid double constant")
}
@ -232,7 +229,7 @@ internal class ConstantBuilder(
override fun visitString(stringType: BuiltinType): CodeBlock {
return if (value.isString) {
CodeBlock.builder().add("\$S", value.asString).build()
CodeBlock.builder().add("\$S", value.getAsString()).build()
} else {
constantOrError("Invalid string constant")
}
@ -249,19 +246,19 @@ internal class ConstantBuilder(
override fun visitEnum(enumType: EnumType): CodeBlock {
// TODO(ben): Figure out how to handle const references
try {
val member = when (value.kind()) {
val member = when (value.kind) {
ConstValueElement.Kind.INTEGER ->
enumType.findMemberById(value.asInt)
enumType.findMemberById(value.getAsInt())
ConstValueElement.Kind.IDENTIFIER -> {
// Remove the enum name prefix, assuming it is present
val id = value.asString.split(".").last()
val id = value.getAsString().split(".").last()
enumType.findMemberByName(id)
}
else -> throw AssertionError(
"Constant value kind " + value.kind() + " is not possibly an enum; validation bug")
"Constant value kind ${value.kind} is not possibly an enum; validation bug")
}
return CodeBlock.builder()
@ -270,13 +267,13 @@ internal class ConstantBuilder(
} catch (e: NoSuchElementException) {
throw IllegalStateException(
"No enum member in " + enumType.name() + " with value " + value.value())
"No enum member in ${enumType.name()} with value ${value.value}")
}
}
override fun visitList(listType: ListType): CodeBlock {
if (value.isList) {
if (value.asList.isEmpty()) {
if (value.getAsList().isEmpty()) {
val elementType = typeResolver.getJavaClass(listType.elementType())
return CodeBlock.builder()
.add("\$T.<\$T>emptyList()", TypeNames.COLLECTIONS, elementType)
@ -290,7 +287,7 @@ internal class ConstantBuilder(
override fun visitSet(setType: SetType): CodeBlock {
if (value.isList) { // not a typo; ConstantValueElement.Kind.LIST covers lists and sets.
if (value.asList.isEmpty()) {
if (value.getAsList().isEmpty()) {
val elementType = typeResolver.getJavaClass(setType.elementType())
return CodeBlock.builder()
.add("\$T.<\$T>emptySet()", TypeNames.COLLECTIONS, elementType)
@ -304,7 +301,7 @@ internal class ConstantBuilder(
override fun visitMap(mapType: MapType): CodeBlock {
if (value.isMap) {
if (value.asMap.isEmpty()) {
if (value.getAsMap().isEmpty()) {
val keyType = typeResolver.getJavaClass(mapType.keyType())
val valueType = typeResolver.getJavaClass(mapType.valueType())
return CodeBlock.builder()
@ -339,7 +336,7 @@ internal class ConstantBuilder(
}
private fun constantOrError(error: String): CodeBlock {
val message = "$error: ${value.value()} + at ${value.location()}"
val message = "$error: ${value.value} + at ${value.location}"
if (!value.isIdentifier) {
throw IllegalStateException(message)
@ -347,7 +344,7 @@ internal class ConstantBuilder(
val expectedType = type.trueType
var name = value.asString
var name = value.getAsString()
val ix = name.indexOf('.')
var expectedProgram: String? = null
if (ix != -1) {

Просмотреть файл

@ -888,7 +888,7 @@ class ThriftyCodeGenerator {
}
override fun visitList(listType: ListType) {
if (constant.value().asList.isEmpty()) {
if (constant.value().getAsList().isEmpty()) {
field.initializer("\$T.emptyList()", TypeNames.COLLECTIONS)
} else {
initCollection("list", "unmodifiableList")
@ -896,7 +896,7 @@ class ThriftyCodeGenerator {
}
override fun visitSet(setType: SetType) {
if (constant.value().asList.isEmpty()) {
if (constant.value().getAsList().isEmpty()) {
field.initializer("\$T.emptySet()", TypeNames.COLLECTIONS)
} else {
initCollection("set", "unmodifiableSet")
@ -904,7 +904,7 @@ class ThriftyCodeGenerator {
}
override fun visitMap(mapType: MapType) {
if (constant.value().asMap.isEmpty()) {
if (constant.value().getAsMap().isEmpty()) {
field.initializer("\$T.emptyMap()", TypeNames.COLLECTIONS)
} else {
initCollection("map", "unmodifiableMap")

9
thrifty-schema/Module.md Normal file
Просмотреть файл

@ -0,0 +1,9 @@
# Module thrifty-schema
# Package com.microsoft.thrifty.schema.parser
Contains a Thrift parser implementation.
The output of the parser is an untyped AST - types are not validated, typedefs are unresolved, etc.

Просмотреть файл

@ -19,7 +19,7 @@
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
plugins {
id 'net.ltgt.apt' version '0.15'
id 'kotlin'
id 'antlr'
}
@ -39,15 +39,22 @@ dependencies {
implementation libraries.okio
api libraries.kotlin
api 'com.google.code.findbugs:jsr305:3.0.1'
api libraries.guava
api 'com.google.auto.value:auto-value-annotations:1.6'
annotationProcessor 'com.google.auto.value:auto-value:1.6'
testImplementation libraries.testing
}
compileTestJava {
options.compilerArgs += [ '-Xep:ImmutableModification:OFF', '-Xlint:deprecation,unchecked' ]
}
// For some reason, Kotlin compilation is being run prior to antlr by default.
tasks['compileKotlin'].dependsOn('generateGrammarSource')
tasks['compileTestKotlin'].dependsOn('generateGrammarSource')
tasks['compileTestKotlin'].dependsOn('generateTestGrammarSource')
tasks['compileJava'].dependsOn('generateGrammarSource')
tasks['compileTestJava'].dependsOn('generateGrammarSource')
tasks['compileTestJava'].dependsOn('generateTestGrammarSource')

Просмотреть файл

@ -20,11 +20,11 @@
*/
package com.microsoft.thrifty.schema;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class ErrorReporter {
private boolean hasError = false;
@ -70,14 +70,46 @@ public class ErrorReporter {
return builder.build();
}
@AutoValue
abstract static class Report {
public abstract Level level();
public abstract Location location();
public abstract String message();
static class Report {
private final Level level;
private final Location location;
private final String message;
public Level level() {
return level;
}
public Location location() {
return location;
}
public String message() {
return message;
}
private Report(Level level, Location location, String message) {
this.level = level;
this.location = location;
this.message = message;
}
static Report create(Level level, Location location, String message) {
return new AutoValue_ErrorReporter_Report(level, location, message);
return new Report(level, location, message);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Report report = (Report) o;
return level == report.level
&& Objects.equals(location, report.location)
&& Objects.equals(message, report.message);
}
@Override
public int hashCode() {
return Objects.hash(level, location, message);
}
}

Просмотреть файл

@ -387,7 +387,7 @@ class Linker {
@Nonnull
ThriftType resolveType(TypeElement type) {
AnnotationElement annotationElement = type.annotations();
ImmutableMap<String, String> annotations = annotationElement != null
Map<String, String> annotations = annotationElement != null
? annotationElement.values()
: ImmutableMap.of();

Просмотреть файл

@ -21,7 +21,6 @@
package com.microsoft.thrifty.schema;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Closeables;
import com.microsoft.thrifty.schema.parser.IncludeElement;
import com.microsoft.thrifty.schema.parser.ThriftFileElement;
@ -261,7 +260,7 @@ public final class Loader {
loadedFiles.put(file.normalize().toAbsolutePath(), element);
ImmutableList<IncludeElement> includes = element.includes();
List<IncludeElement> includes = element.includes();
if (includes.size() > 0) {
includePaths.addFirst(dir);
for (IncludeElement include : includes) {

Просмотреть файл

@ -1,56 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.microsoft.thrifty.schema.Location;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
@AutoValue
public abstract class AnnotationElement {
public abstract Location location();
public abstract ImmutableMap<String, String> values();
@Nullable
public String get(@Nonnull String name) {
return values().get(name);
}
public boolean containsKey(@Nonnull String name) {
return values().containsKey(name);
}
public boolean isEmpty() {
return values().isEmpty();
}
public int size() {
return values().size();
}
public static AnnotationElement create(Location location, Map<String, String> values) {
return new AutoValue_AnnotationElement(location, ImmutableMap.copyOf(values));
}
}

Просмотреть файл

@ -1,57 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.microsoft.thrifty.schema.Location;
import java.util.UUID;
@AutoValue
public abstract class ConstElement {
public abstract Location location();
public abstract String documentation();
public abstract TypeElement type();
public abstract String name();
public abstract UUID uuid();
public abstract ConstValueElement value();
public static Builder builder(Location location) {
return new AutoValue_ConstElement.Builder()
.location(location)
.documentation("")
.uuid(ThriftyParserPlugins.createUUID());
}
ConstElement() { }
@AutoValue.Builder
public interface Builder {
Builder location(Location location);
Builder documentation(String documentation);
Builder type(TypeElement type);
Builder name(String name);
Builder uuid(UUID uuid);
Builder value(ConstValueElement value);
ConstElement build();
}
}

Просмотреть файл

@ -1,149 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.microsoft.thrifty.schema.Location;
import java.util.List;
import java.util.Map;
@AutoValue
public abstract class ConstValueElement {
public enum Kind {
INTEGER,
DOUBLE,
STRING,
IDENTIFIER,
LIST,
MAP,
}
public abstract Location location();
public abstract Kind kind();
public abstract String thriftText();
public abstract Object value();
public boolean isInt() {
return kind() == Kind.INTEGER;
}
public boolean isDouble() {
return kind() == Kind.DOUBLE;
}
public boolean isString() {
return kind() == Kind.STRING;
}
public boolean isIdentifier() {
return kind() == Kind.IDENTIFIER;
}
public boolean isList() {
return kind() == Kind.LIST;
}
public boolean isMap() {
return kind() == Kind.MAP;
}
public String getAsString() {
if (kind() == Kind.STRING || kind() == Kind.IDENTIFIER) {
return (String) value();
} else {
throw new IllegalStateException("Cannot convert to string, kind=" + kind());
}
}
public long getAsLong() {
if (kind() == Kind.INTEGER) {
return (Long) value();
} else {
throw new IllegalStateException("Cannot convert to long, kind=" + kind());
}
}
public int getAsInt() {
if (kind() == Kind.INTEGER) {
return ((Long) value()).intValue();
} else {
throw new IllegalStateException("Cannot convert to long, kind=" + kind());
}
}
public double getAsDouble() {
if (kind() == Kind.DOUBLE) {
return (Double) value();
} else {
throw new IllegalStateException("Cannot convert to double, kind=" + kind());
}
}
@SuppressWarnings("unchecked")
public List<ConstValueElement> getAsList() {
if (kind() == Kind.LIST) {
return (List<ConstValueElement>) value();
} else {
throw new IllegalStateException("Cannot convert to list, kind=" + kind());
}
}
@SuppressWarnings("unchecked")
public Map<ConstValueElement, ConstValueElement> getAsMap() {
if (kind() == Kind.MAP) {
return (Map<ConstValueElement, ConstValueElement>) value();
} else {
throw new IllegalStateException("Cannot convert to map, kind=" + kind());
}
}
ConstValueElement() { }
public static ConstValueElement integer(Location location, String text, long value) {
return new AutoValue_ConstValueElement(location, Kind.INTEGER, text, value);
}
public static ConstValueElement real(Location location, String text, double value) {
return new AutoValue_ConstValueElement(location, Kind.DOUBLE, text, value);
}
public static ConstValueElement literal(Location location, String text, String value) {
return new AutoValue_ConstValueElement(location, Kind.STRING, text, value);
}
public static ConstValueElement identifier(Location location, String text, String value) {
return new AutoValue_ConstValueElement(location, Kind.IDENTIFIER, text, value);
}
public static ConstValueElement list(Location location, String text, List<ConstValueElement> elements) {
return new AutoValue_ConstValueElement(location, Kind.LIST, text, ImmutableList.copyOf(elements));
}
public static ConstValueElement map(
Location location,
String text,
Map<ConstValueElement, ConstValueElement> elements) {
return new AutoValue_ConstValueElement(location, Kind.MAP, text, ImmutableMap.copyOf(elements));
}
}

Просмотреть файл

@ -1,61 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.microsoft.thrifty.schema.Location;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
@AutoValue
public abstract class EnumElement {
public abstract Location location();
public abstract String documentation();
public abstract String name();
public abstract UUID uuid();
public abstract ImmutableList<EnumMemberElement> members();
@Nullable public abstract AnnotationElement annotations();
EnumElement() { }
public static Builder builder(Location location) {
return new AutoValue_EnumElement.Builder()
.location(location)
.documentation("")
.uuid(ThriftyParserPlugins.createUUID());
}
@AutoValue.Builder
public interface Builder {
Builder location(Location location);
Builder documentation(String documentation);
Builder name(String name);
Builder uuid(UUID uuid);
Builder members(List<EnumMemberElement> members);
Builder annotations(AnnotationElement annotations);
EnumElement build();
}
}

Просмотреть файл

@ -1,59 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.microsoft.thrifty.schema.Location;
import java.util.UUID;
import javax.annotation.Nullable;
@AutoValue
public abstract class EnumMemberElement {
public abstract Location location();
public abstract String documentation();
public abstract String name();
public abstract UUID uuid();
public abstract int value();
@Nullable public abstract AnnotationElement annotations();
public static Builder builder(Location location) {
return new AutoValue_EnumMemberElement.Builder()
.location(location)
.documentation("")
.uuid(ThriftyParserPlugins.createUUID());
}
EnumMemberElement() { }
@AutoValue.Builder
public interface Builder {
Builder location(Location location);
Builder documentation(String documentation);
Builder name(String name);
Builder uuid(UUID uuid);
Builder value(int value);
Builder annotations(AnnotationElement annotations);
EnumMemberElement build();
}
}

Просмотреть файл

@ -1,67 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.microsoft.thrifty.schema.Location;
import com.microsoft.thrifty.schema.Requiredness;
import java.util.UUID;
import javax.annotation.Nullable;
@AutoValue
public abstract class FieldElement {
public static Builder builder(Location location) {
return new AutoValue_FieldElement.Builder()
.location(location)
.documentation("")
.requiredness(Requiredness.DEFAULT)
.uuid(ThriftyParserPlugins.createUUID());
}
public abstract Location location();
public abstract String documentation();
public abstract int fieldId();
public abstract Requiredness requiredness();
public abstract TypeElement type();
public abstract String name();
public abstract UUID uuid();
@Nullable public abstract ConstValueElement constValue();
@Nullable public abstract AnnotationElement annotations();
FieldElement() { }
@AutoValue.Builder
public interface Builder {
Builder location(Location location);
Builder documentation(String documentation);
Builder fieldId(int fieldId);
Builder requiredness(Requiredness requiredness);
Builder type(TypeElement type);
Builder name(String name);
Builder uuid(UUID uuid);
Builder constValue(ConstValueElement constValue);
Builder annotations(AnnotationElement annotations);
FieldElement build();
}
}

Просмотреть файл

@ -1,70 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.microsoft.thrifty.schema.Location;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
@AutoValue
public abstract class FunctionElement {
public static Builder builder(Location location) {
return new AutoValue_FunctionElement.Builder()
.location(location)
.documentation("")
.oneWay(false)
.params(ImmutableList.of())
.exceptions(ImmutableList.of())
.uuid(ThriftyParserPlugins.createUUID());
}
public abstract Location location();
public abstract String documentation();
public abstract boolean oneWay();
public abstract TypeElement returnType();
public abstract String name();
public abstract UUID uuid();
public abstract ImmutableList<FieldElement> params();
public abstract ImmutableList<FieldElement> exceptions();
@Nullable public abstract AnnotationElement annotations();
FunctionElement() { }
@AutoValue.Builder
public interface Builder {
Builder location(Location location);
Builder documentation(String documentation);
Builder oneWay(boolean oneWay);
Builder returnType(TypeElement returnType);
Builder name(String name);
Builder uuid(UUID uuid);
Builder params(List<FieldElement> params);
Builder exceptions(List<FieldElement> params);
Builder annotations(AnnotationElement annotations);
FunctionElement build();
}
}

Просмотреть файл

@ -1,37 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.microsoft.thrifty.schema.Location;
@AutoValue
public abstract class IncludeElement {
public abstract Location location();
public abstract boolean isCpp();
public abstract String path();
IncludeElement() { }
public static IncludeElement create(Location location, boolean isCpp, String path) {
return new AutoValue_IncludeElement(location, isCpp, path);
}
}

Просмотреть файл

@ -1,40 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.microsoft.thrifty.schema.Location;
@AutoValue
public abstract class ListTypeElement extends TypeElement {
public abstract TypeElement elementType();
ListTypeElement() {
}
public static ListTypeElement create(
Location location,
TypeElement element,
AnnotationElement annotations) {
String name = "list<" + element.name() + ">";
return new AutoValue_ListTypeElement(location, name, annotations, element);
}
}

Просмотреть файл

@ -1,42 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.microsoft.thrifty.schema.Location;
@AutoValue
public abstract class MapTypeElement extends TypeElement {
public abstract TypeElement keyType();
public abstract TypeElement valueType();
MapTypeElement() {
}
public static MapTypeElement create(
Location location,
TypeElement key,
TypeElement value,
AnnotationElement annotations) {
String name = "map<" + key.name() + ", " + value.name() + ">";
return new AutoValue_MapTypeElement(location, name, annotations, key, value);
}
}

Просмотреть файл

@ -1,55 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.microsoft.thrifty.schema.Location;
import com.microsoft.thrifty.schema.NamespaceScope;
import javax.annotation.Nullable;
@AutoValue
public abstract class NamespaceElement {
public abstract Location location();
public abstract NamespaceScope scope();
public abstract String namespace();
@Nullable
public abstract AnnotationElement annotations();
NamespaceElement() { }
public static Builder builder(Location location) {
return new AutoValue_NamespaceElement.Builder()
.location(location)
.annotations(null);
}
@AutoValue.Builder
public interface Builder {
Builder location(Location location);
Builder scope(NamespaceScope scope);
Builder namespace(String namespace);
Builder annotations(AnnotationElement annotations);
NamespaceElement build();
}
}

Просмотреть файл

@ -1,37 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.microsoft.thrifty.schema.Location;
@AutoValue
public abstract class ScalarTypeElement extends TypeElement {
ScalarTypeElement() {
}
public static ScalarTypeElement create(
Location location,
String name,
AnnotationElement annotations) {
return new AutoValue_ScalarTypeElement(location, name, annotations);
}
}

Просмотреть файл

@ -1,64 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.microsoft.thrifty.schema.Location;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
@AutoValue
public abstract class ServiceElement {
public abstract Location location();
public abstract String documentation();
public abstract String name();
public abstract UUID uuid();
@Nullable public abstract TypeElement extendsService();
public abstract ImmutableList<FunctionElement> functions();
@Nullable public abstract AnnotationElement annotations();
ServiceElement() { }
public static Builder builder(Location location) {
return new AutoValue_ServiceElement.Builder()
.location(location)
.documentation("")
.functions(ImmutableList.of())
.uuid(ThriftyParserPlugins.createUUID());
}
@AutoValue.Builder
public interface Builder {
Builder location(Location location);
Builder documentation(String documentation);
Builder name(String name);
Builder uuid(UUID uuid);
Builder extendsService(TypeElement serviceName);
Builder functions(List<FunctionElement> functions);
Builder annotations(AnnotationElement annotations);
ServiceElement build();
}
}

Просмотреть файл

@ -1,40 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.microsoft.thrifty.schema.Location;
@AutoValue
public abstract class SetTypeElement extends TypeElement {
public abstract TypeElement elementType();
SetTypeElement() {
}
public static SetTypeElement create(
Location location,
TypeElement element,
AnnotationElement annotations) {
String name = "set<" + element.name() + ">";
return new AutoValue_SetTypeElement(location, name, annotations, element);
}
}

Просмотреть файл

@ -1,73 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.microsoft.thrifty.schema.Location;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
/**
* Represents one of the three aggregate Thrift declarations: struct, union, or exception.
*/
@AutoValue
public abstract class StructElement {
public abstract Location location();
public abstract String documentation();
public abstract Type type();
public abstract UUID uuid();
public abstract String name();
public abstract ImmutableList<FieldElement> fields();
@Nullable
public abstract AnnotationElement annotations();
public static Builder builder(Location location) {
return new AutoValue_StructElement.Builder()
.location(location)
.documentation("")
.uuid(ThriftyParserPlugins.createUUID());
}
StructElement() { }
@AutoValue.Builder
public interface Builder {
Builder location(Location location);
Builder documentation(String documentation);
Builder type(Type type);
Builder uuid(UUID uuid);
Builder name(String name);
Builder fields(List<FieldElement> fields);
Builder annotations(AnnotationElement annotations);
StructElement build();
}
public enum Type {
STRUCT,
UNION,
EXCEPTION
}
}

Просмотреть файл

@ -1,73 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.microsoft.thrifty.schema.Location;
import java.util.List;
@AutoValue
public abstract class ThriftFileElement {
public abstract Location location();
public abstract ImmutableList<NamespaceElement> namespaces();
public abstract ImmutableList<IncludeElement> includes();
public abstract ImmutableList<ConstElement> constants();
public abstract ImmutableList<TypedefElement> typedefs();
public abstract ImmutableList<EnumElement> enums();
public abstract ImmutableList<StructElement> structs();
public abstract ImmutableList<StructElement> unions();
public abstract ImmutableList<StructElement> exceptions();
public abstract ImmutableList<ServiceElement> services();
public static Builder builder(Location location) {
return new AutoValue_ThriftFileElement.Builder()
.location(location)
.namespaces(ImmutableList.of())
.includes(ImmutableList.of())
.constants(ImmutableList.of())
.typedefs(ImmutableList.of())
.enums(ImmutableList.of())
.structs(ImmutableList.of())
.unions(ImmutableList.of())
.exceptions(ImmutableList.of())
.services(ImmutableList.of());
}
ThriftFileElement() { }
@AutoValue.Builder
public interface Builder {
Builder location(Location location);
Builder namespaces(List<NamespaceElement> namespaces);
Builder includes(List<IncludeElement> includes);
Builder constants(List<ConstElement> constants);
Builder typedefs(List<TypedefElement> typedefs);
Builder enums(List<EnumElement> enums);
Builder structs(List<StructElement> structs);
Builder unions(List<StructElement> unions);
Builder exceptions(List<StructElement> exceptions);
Builder services(List<ServiceElement> services);
ThriftFileElement build();
}
}

Просмотреть файл

@ -1,831 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.microsoft.thrifty.schema.ErrorReporter;
import com.microsoft.thrifty.schema.Location;
import com.microsoft.thrifty.schema.NamespaceScope;
import com.microsoft.thrifty.schema.Requiredness;
import com.microsoft.thrifty.schema.antlr.AntlrThriftBaseListener;
import com.microsoft.thrifty.schema.antlr.AntlrThriftLexer;
import com.microsoft.thrifty.schema.antlr.AntlrThriftParser;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A set of callbacks that, when used with a {@link org.antlr.v4.runtime.tree.ParseTreeWalker},
* assemble a {@link ThriftFileElement} from an {@link AntlrThriftParser}.
*
* Instances of this class are single-use; after walking a parse tree, it will contain
* that parser's state as thrifty-schema parser elements.
*/
class ThriftListener extends AntlrThriftBaseListener {
// A number of tokens that should comfortably accommodate most input files
// without wildly re-allocating. Estimated based on the ClientTestThrift
// and TestThrift files, which contain around ~1200 tokens each.
private static final int INITIAL_BITSET_CAPACITY = 2048;
private final CommonTokenStream tokenStream;
private final ErrorReporter errorReporter;
private final Location location;
// We need to record which comment tokens have been treated as trailing documentation,
// so that scanning for leading doc tokens for subsequent elements knows where to stop.
// We can do this with a bitset tracking token indices of trailing-comment tokens.
private final BitSet trailingDocTokenIndexes = new BitSet(INITIAL_BITSET_CAPACITY);
private final List<IncludeElement> includes = new ArrayList<>();
private final List<NamespaceElement> namespaces = new ArrayList<>();
private final List<EnumElement> enums = new ArrayList<>();
private final List<TypedefElement> typedefs = new ArrayList<>();
private final List<StructElement> structs = new ArrayList<>();
private final List<StructElement> unions = new ArrayList<>();
private final List<StructElement> exceptions = new ArrayList<>();
private final List<ConstElement> consts = new ArrayList<>();
private final List<ServiceElement> services = new ArrayList<>();
/**
* Creates a new ThriftListener instance.
*
* @param tokenStream the same token stream used with the corresponding {@link AntlrThriftParser};
* this stream will be queried for "hidden" tokens containing parsed doc
* comments.
* @param errorReporter an error reporting mechanism, used to communicate errors during parsing.
* @param location a location pointing at the beginning of the file being parsed.
*/
ThriftListener(CommonTokenStream tokenStream, ErrorReporter errorReporter, Location location) {
this.tokenStream = tokenStream;
this.errorReporter = errorReporter;
this.location = location;
}
ThriftFileElement buildFileElement() {
return ThriftFileElement.builder(location)
.includes(includes)
.namespaces(namespaces)
.typedefs(typedefs)
.enums(enums)
.structs(structs)
.unions(unions)
.exceptions(exceptions)
.constants(consts)
.services(services)
.build();
}
@Override
public void exitInclude(AntlrThriftParser.IncludeContext ctx) {
TerminalNode pathNode = ctx.LITERAL();
String path = unquote(locationOf(pathNode), pathNode.getText(), false);
includes.add(IncludeElement.create(locationOf(ctx), false, path));
}
@Override
public void exitCppInclude(AntlrThriftParser.CppIncludeContext ctx) {
TerminalNode pathNode = ctx.LITERAL();
String path = unquote(locationOf(pathNode), pathNode.getText(), false);
includes.add(IncludeElement.create(locationOf(ctx), true, path));
}
@Override
public void exitStandardNamespace(AntlrThriftParser.StandardNamespaceContext ctx) {
String scopeName = ctx.namespaceScope().getText();
String name = ctx.ns.getText();
AnnotationElement annotations = annotationsFromAntlr(ctx.annotationList());
NamespaceScope scope = NamespaceScope.forThriftName(scopeName);
if (scope == null) {
errorReporter.warn(locationOf(ctx.namespaceScope()), "Unknown namespace scope '" + scopeName + "'");
return;
}
NamespaceElement element = NamespaceElement.builder(locationOf(ctx))
.scope(scope)
.namespace(name)
.annotations(annotations)
.build();
namespaces.add(element);
}
@Override
public void exitPhpNamespace(AntlrThriftParser.PhpNamespaceContext ctx) {
NamespaceElement element = NamespaceElement.builder(locationOf(ctx))
.scope(NamespaceScope.PHP)
.namespace(unquote(locationOf(ctx.LITERAL()), ctx.LITERAL().getText()))
.annotations(annotationsFromAntlr(ctx.annotationList()))
.build();
namespaces.add(element);
}
@Override
public void exitXsdNamespace(AntlrThriftParser.XsdNamespaceContext ctx) {
errorReporter.error(locationOf(ctx), "'xsd_namespace' is unsupported");
}
@Override
public void exitSenum(AntlrThriftParser.SenumContext ctx) {
errorReporter.error(locationOf(ctx), "'senum' is unsupported; use 'enum' instead");
}
@Override
public void exitEnumDef(AntlrThriftParser.EnumDefContext ctx) {
String enumName = ctx.IDENTIFIER().getText();
int nextValue = 0;
Set<Integer> values = new HashSet<>();
List<EnumMemberElement> members = new ArrayList<>(ctx.enumMember().size());
for (AntlrThriftParser.EnumMemberContext memberContext : ctx.enumMember()) {
int value = nextValue;
TerminalNode valueToken = memberContext.INTEGER();
if (valueToken != null) {
value = parseInt(valueToken);
}
if (!values.add(value)) {
errorReporter.error(locationOf(memberContext), "duplicate enum value: " + value);
continue;
}
nextValue = value + 1;
EnumMemberElement element = EnumMemberElement.builder(locationOf(memberContext))
.name(memberContext.IDENTIFIER().getText())
.value(value)
.documentation(formatJavadoc(memberContext))
.annotations(annotationsFromAntlr(memberContext.annotationList()))
.build();
members.add(element);
}
String doc = formatJavadoc(ctx);
EnumElement element = EnumElement.builder(locationOf(ctx))
.name(enumName)
.documentation(doc)
.annotations(annotationsFromAntlr(ctx.annotationList()))
.members(members)
.build();
enums.add(element);
}
@Override
public void exitStructDef(AntlrThriftParser.StructDefContext ctx) {
String name = ctx.IDENTIFIER().getText();
ImmutableList<FieldElement> fields = parseFieldList(ctx.field());
StructElement element = StructElement.builder(locationOf(ctx))
.name(name)
.fields(fields)
.type(StructElement.Type.STRUCT)
.documentation(formatJavadoc(ctx))
.annotations(annotationsFromAntlr(ctx.annotationList()))
.build();
structs.add(element);
}
@Override
public void exitUnionDef(AntlrThriftParser.UnionDefContext ctx) {
String name = ctx.IDENTIFIER().getText();
ImmutableList<FieldElement> fields = parseFieldList(ctx.field());
int numFieldsWithDefaultValues = 0;
for (int i = 0; i < fields.size(); ++i) {
FieldElement element = fields.get(i);
if (element.requiredness() == Requiredness.REQUIRED) {
AntlrThriftParser.FieldContext fieldContext = ctx.field(i);
errorReporter.error(locationOf(fieldContext), "unions cannot have required fields");
}
if (element.constValue() != null) {
++numFieldsWithDefaultValues;
}
}
if (numFieldsWithDefaultValues > 1) {
errorReporter.error(locationOf(ctx), "unions can have at most one default value");
}
StructElement element = StructElement.builder(locationOf(ctx))
.name(name)
.fields(fields)
.type(StructElement.Type.UNION)
.documentation(formatJavadoc(ctx))
.annotations(annotationsFromAntlr(ctx.annotationList()))
.build();
unions.add(element);
}
@Override
public void exitExceptionDef(AntlrThriftParser.ExceptionDefContext ctx) {
String name = ctx.IDENTIFIER().getText();
ImmutableList<FieldElement> fields = parseFieldList(ctx.field());
StructElement element = StructElement.builder(locationOf(ctx))
.name(name)
.fields(fields)
.type(StructElement.Type.EXCEPTION)
.documentation(formatJavadoc(ctx))
.annotations(annotationsFromAntlr(ctx.annotationList()))
.build();
exceptions.add(element);
}
private ImmutableList<FieldElement> parseFieldList(List<AntlrThriftParser.FieldContext> contexts) {
return parseFieldList(contexts, Requiredness.DEFAULT);
}
private ImmutableList<FieldElement> parseFieldList(
List<AntlrThriftParser.FieldContext> contexts,
Requiredness defaultRequiredness) {
ImmutableList.Builder<FieldElement> builder = ImmutableList.builder();
Set<Integer> ids = new HashSet<>();
int nextValue = 1;
for (AntlrThriftParser.FieldContext fieldContext : contexts) {
FieldElement element = parseField(nextValue, fieldContext, defaultRequiredness);
if (element != null) {
builder = builder.add(element);
if (!ids.add(element.fieldId())) {
errorReporter.error(locationOf(fieldContext), "duplicate field ID: " + element.fieldId());
}
if (element.fieldId() <= 0) {
errorReporter.error(locationOf(fieldContext), "field ID must be greater than zero");
}
if (element.fieldId() >= nextValue) {
nextValue = element.fieldId() + 1;
}
} else {
// assert-fail here?
++nextValue; // this represents an error condition
}
}
return builder.build();
}
private FieldElement parseField(
int defaultValue,
AntlrThriftParser.FieldContext ctx,
Requiredness defaultRequiredness) {
int fieldId = defaultValue;
if (ctx.INTEGER() != null) {
fieldId = parseInt(ctx.INTEGER());
}
String fieldName = ctx.IDENTIFIER().getText();
Requiredness requiredness = defaultRequiredness;
if (ctx.requiredness() != null) {
if (ctx.requiredness().getText().equals("required")) {
requiredness = Requiredness.REQUIRED;
} else if (ctx.requiredness().getText().equals("optional")) {
requiredness = Requiredness.OPTIONAL;
} else {
throw new AssertionError("Unexpected requiredness value: " + ctx.requiredness().getText());
}
}
return FieldElement.builder(locationOf(ctx))
.documentation(formatJavadoc(ctx))
.fieldId(fieldId)
.requiredness(requiredness)
.type(typeElementOf(ctx.fieldType()))
.name(fieldName)
.annotations(annotationsFromAntlr(ctx.annotationList()))
.constValue(constValueElementOf(ctx.constValue()))
.build();
}
@Override
public void exitTypedef(AntlrThriftParser.TypedefContext ctx) {
TypeElement oldType = typeElementOf(ctx.fieldType());
TypedefElement typedef = TypedefElement.builder(locationOf(ctx))
.documentation(formatJavadoc(ctx))
.annotations(annotationsFromAntlr(ctx.annotationList()))
.oldType(oldType)
.newName(ctx.IDENTIFIER().getText())
.build();
typedefs.add(typedef);
}
@Override
public void exitConstDef(AntlrThriftParser.ConstDefContext ctx) {
ConstElement element = ConstElement.builder(locationOf(ctx))
.documentation(formatJavadoc(ctx))
.type(typeElementOf(ctx.fieldType()))
.name(ctx.IDENTIFIER().getText())
.value(constValueElementOf(ctx.constValue()))
.build();
consts.add(element);
}
@Override
public void exitServiceDef(AntlrThriftParser.ServiceDefContext ctx) {
String name = ctx.name.getText();
ServiceElement.Builder builder = ServiceElement.builder(locationOf(ctx))
.name(name)
.functions(parseFunctionList(ctx.function()))
.documentation(formatJavadoc(ctx))
.annotations(annotationsFromAntlr(ctx.annotationList()));
if (ctx.superType != null) {
TypeElement superType = typeElementOf(ctx.superType);
if (!(superType instanceof ScalarTypeElement)) {
errorReporter.error(locationOf(ctx), "services cannot extend collections");
}
builder = builder.extendsService(superType);
}
services.add(builder.build());
}
private ImmutableList<FunctionElement> parseFunctionList(List<AntlrThriftParser.FunctionContext> functionContexts) {
ImmutableList.Builder<FunctionElement> functions = ImmutableList.builder();
for (AntlrThriftParser.FunctionContext ctx : functionContexts) {
String name = ctx.IDENTIFIER().getText();
TypeElement returnType;
if (ctx.fieldType() != null) {
returnType = typeElementOf(ctx.fieldType());
} else {
TerminalNode token = ctx.getToken(AntlrThriftLexer.VOID, 0);
if (token == null) {
throw new AssertionError("Function has no return type, and no VOID token - grammar error");
}
Location loc = locationOf(token);
// Do people actually annotation 'void'? We'll find out!
returnType = TypeElement.scalar(loc, "void", null);
}
boolean isOneway = ctx.ONEWAY() != null;
FunctionElement.Builder builder = FunctionElement.builder(locationOf(ctx))
.oneWay(isOneway)
.returnType(returnType)
.name(name)
.documentation(formatJavadoc(ctx))
.annotations(annotationsFromAntlr(ctx.annotationList()))
.params(parseFieldList(ctx.fieldList().field(), Requiredness.REQUIRED));
if (ctx.throwsList() != null) {
builder = builder.exceptions(parseFieldList(ctx.throwsList().fieldList().field()));
}
functions.add(builder.build());
}
return functions.build();
}
@Override
public void visitErrorNode(ErrorNode node) {
errorReporter.error(locationOf(node), node.getText());
}
// region Utilities
private AnnotationElement annotationsFromAntlr(AntlrThriftParser.AnnotationListContext ctx) {
if (ctx == null) {
return null;
}
Map<String, String> annotations = new LinkedHashMap<>();
for (AntlrThriftParser.AnnotationContext annotationContext : ctx.annotation()) {
String name = annotationContext.IDENTIFIER().getText();
String value;
if (annotationContext.LITERAL() != null) {
value = unquote(locationOf(annotationContext.LITERAL()), annotationContext.LITERAL().getText());
} else {
value = "true";
}
annotations.put(name, value);
}
return AnnotationElement.create(locationOf(ctx), annotations);
}
private Location locationOf(ParserRuleContext ctx) {
return locationOf(ctx.getStart());
}
private Location locationOf(TerminalNode node) {
return locationOf(node.getSymbol());
}
private Location locationOf(Token token) {
int line = token.getLine();
int col = token.getCharPositionInLine() + 1; // Location.col is 1-based, Token.col is 0-based
return location.at(line, col);
}
private String unquote(Location location, String literal) {
return unquote(location, literal, /* processEscapes */ true);
}
private String unquote(Location location, String literal, boolean processEscapes) {
char[] chars = literal.toCharArray();
char startChar = chars[0];
char endChar = chars[chars.length - 1];
if (startChar != endChar || (startChar != '\'' && startChar != '"')) {
throw new AssertionError("Incorrect UNESCAPED_LITERAL rule: " + literal);
}
StringBuilder sb = new StringBuilder(literal.length() - 2);
int i = 1;
int end = chars.length - 1;
while (i < end) {
char c = chars[i++];
if (processEscapes && c == '\\') {
if (i == end) {
errorReporter.error(location, "Unterminated literal");
break;
}
char escape = chars[i++];
switch (escape) {
case 'a':
sb.append((char) 0x7);
break;
case 'b':
sb.append('\b');
break;
case 'f':
sb.append('\f');
break;
case 'n':
sb.append('\n');
break;
case 'r':
sb.append('\r');
break;
case 't':
sb.append('\t');
break;
case 'v':
sb.append((char) 0xB);
break;
case '\\':
sb.append('\\');
break;
case 'u':
throw new UnsupportedOperationException("unicode escapes not yet implemented");
default:
if (escape == startChar) {
sb.append(startChar);
} else {
errorReporter.error(location, "invalid escape character: " + escape);
}
}
} else {
sb.append(c);
}
}
return sb.toString();
}
private TypeElement typeElementOf(AntlrThriftParser.FieldTypeContext context) {
if (context.baseType() != null) {
if (context.baseType().getText().equals("slist")) {
errorReporter.error(locationOf(context), "slist is unsupported; use list<string> instead");
}
return TypeElement.scalar(
locationOf(context),
context.baseType().getText(),
annotationsFromAntlr(context.annotationList()));
}
if (context.IDENTIFIER() != null) {
return TypeElement.scalar(
locationOf(context),
context.IDENTIFIER().getText(),
annotationsFromAntlr(context.annotationList()));
}
if (context.containerType() != null) {
AntlrThriftParser.ContainerTypeContext containerContext = context.containerType();
if (containerContext.mapType() != null) {
TypeElement keyType = typeElementOf(containerContext.mapType().key);
TypeElement valueType = typeElementOf(containerContext.mapType().value);
return TypeElement.map(
locationOf(containerContext.mapType()),
keyType,
valueType,
annotationsFromAntlr(context.annotationList()));
}
if (containerContext.setType() != null) {
return TypeElement.set(
locationOf(containerContext.setType()),
typeElementOf(containerContext.setType().fieldType()),
annotationsFromAntlr(context.annotationList()));
}
if (containerContext.listType() != null) {
return TypeElement.list(
locationOf(containerContext.listType()),
typeElementOf(containerContext.listType().fieldType()),
annotationsFromAntlr(context.annotationList()));
}
throw new AssertionError("Unexpected container type - grammar error!");
}
throw new AssertionError("Unexpected type - grammar error!");
}
private ConstValueElement constValueElementOf(AntlrThriftParser.ConstValueContext ctx) {
if (ctx == null) {
return null;
}
if (ctx.INTEGER() != null) {
String text = ctx.INTEGER().getText();
int radix = 10;
if (text.startsWith("0x") || text.startsWith("0X")) {
text = text.substring(2);
radix = 16;
}
try {
long value = Long.parseLong(text, radix);
return ConstValueElement.integer(locationOf(ctx), ctx.INTEGER().getText(), value);
} catch (NumberFormatException e) {
throw new AssertionError("Invalid integer accepted by ANTLR grammar: " + ctx.INTEGER().getText());
}
}
if (ctx.DOUBLE() != null) {
String text = ctx.DOUBLE().getText();
try {
double value = Double.parseDouble(text);
return ConstValueElement.real(locationOf(ctx), ctx.DOUBLE().getText(), value);
} catch (NumberFormatException e) {
throw new AssertionError("Invalid double accepted by ANTLR grammar: " + text);
}
}
if (ctx.LITERAL() != null) {
String text = unquote(locationOf(ctx.LITERAL()), ctx.LITERAL().getText());
return ConstValueElement.literal(locationOf(ctx), ctx.LITERAL().getText(), text);
}
if (ctx.IDENTIFIER() != null) {
String id = ctx.IDENTIFIER().getText();
return ConstValueElement.identifier(locationOf(ctx), ctx.IDENTIFIER().getText(), id);
}
if (ctx.constList() != null) {
ImmutableList.Builder<ConstValueElement> values = ImmutableList.builder();
for (AntlrThriftParser.ConstValueContext valueContext : ctx.constList().constValue()) {
values.add(constValueElementOf(valueContext));
}
return ConstValueElement.list(locationOf(ctx), ctx.constList().getText(), values.build());
}
if (ctx.constMap() != null) {
ImmutableMap.Builder<ConstValueElement, ConstValueElement> values = ImmutableMap.builder();
for (AntlrThriftParser.ConstMapEntryContext entry : ctx.constMap().constMapEntry()) {
ConstValueElement key = constValueElementOf(entry.key);
ConstValueElement value = constValueElementOf(entry.value);
values.put(key, value);
}
return ConstValueElement.map(locationOf(ctx), ctx.constMap().getText(), values.build());
}
throw new AssertionError("unreachable");
}
private static boolean isComment(Token token) {
switch (token.getType()) {
case AntlrThriftLexer.SLASH_SLASH_COMMENT:
case AntlrThriftLexer.HASH_COMMENT:
case AntlrThriftLexer.MULTILINE_COMMENT:
return true;
default:
return false;
}
}
private String formatJavadoc(ParserRuleContext context) {
List<Token> tokens = new ArrayList<>();
tokens.addAll(getLeadingComments(context.getStart()));
tokens.addAll(getTrailingComments(context.getStop()));
return formatJavadoc(tokens);
}
private List<Token> getLeadingComments(Token token) {
List<Token> hiddenTokens = tokenStream.getHiddenTokensToLeft(token.getTokenIndex(), Lexer.HIDDEN);
if (hiddenTokens == null || hiddenTokens.isEmpty()) {
return Collections.emptyList();
}
List<Token> comments = new ArrayList<>(hiddenTokens.size());
for (Token hiddenToken : hiddenTokens) {
if (isComment(hiddenToken) && !trailingDocTokenIndexes.get(hiddenToken.getTokenIndex())) {
comments.add(hiddenToken);
}
}
return comments;
}
/**
* Read comments following the given token, until the first newline is encountered.
*
* INVARIANT:
* Assumes that the parse tree is being walked top-down, left to right!
*
* Trailing-doc tokens are marked as such, so that subsequent searches for "leading"
* doc don't grab tokens already used as "trailing" doc. If the walk order is *not*
* top-down, left-to-right, then the assumption underpinning the separation of leading
* and trailing comments is broken.
*
* @param endToken the token from which to search for trailing comment tokens.
* @return a list, possibly empty, of all trailing comment tokens.
*/
private List<Token> getTrailingComments(Token endToken) {
List<Token> hiddenTokens = tokenStream.getHiddenTokensToRight(endToken.getTokenIndex(), Lexer.HIDDEN);
if (hiddenTokens == null || hiddenTokens.isEmpty()) {
return Collections.emptyList();
}
Token maybeTrailingDoc = hiddenTokens.get(0); // only one trailing comment is possible
if (isComment(maybeTrailingDoc)) {
trailingDocTokenIndexes.set(maybeTrailingDoc.getTokenIndex());
return Collections.singletonList(maybeTrailingDoc);
}
return Collections.emptyList();
}
private static String formatJavadoc(List<Token> commentTokens) {
StringBuilder sb = new StringBuilder();
for (Token token : commentTokens) {
String text = token.getText();
switch (token.getType()) {
case AntlrThriftLexer.SLASH_SLASH_COMMENT:
formatSingleLineComment(sb, text, "//");
break;
case AntlrThriftLexer.HASH_COMMENT:
formatSingleLineComment(sb, text, "#");
break;
case AntlrThriftLexer.MULTILINE_COMMENT:
formatMultilineComment(sb, text);
break;
default:
// wut
break;
}
}
String doc = sb.toString().trim();
if (!Strings.isNullOrEmpty(doc) && !doc.endsWith("\n")) {
doc += "\n";
}
return doc;
}
private static void formatSingleLineComment(StringBuilder sb, String text, String prefix) {
int start = prefix.length();
int end = text.length();
while (start < end && Character.isWhitespace(text.charAt(start))) {
++start;
}
while (end > start && Character.isWhitespace(text.charAt(end - 1))) {
--end;
}
if (start != end) {
sb.append(text.substring(start, end));
}
sb.append("\n");
}
private static void formatMultilineComment(StringBuilder sb, String text) {
char[] chars = text.toCharArray();
int pos = "/*".length();
int length = chars.length;
boolean isStartOfLine = true;
for (; pos + 1 < length; ++pos) {
char c = chars[pos];
if (c == '*' && chars[pos + 1] == '/') {
sb.append("\n");
return;
}
if (c == '\n') {
sb.append(c);
isStartOfLine = true;
} else if (!isStartOfLine) {
sb.append(c);
} else if (c == '*') {
// skip a single subsequent space, if it exists
if (chars[pos + 1] == ' ') {
pos += 1;
}
isStartOfLine = false;
} else if (! Character.isWhitespace(c)) {
sb.append(c);
isStartOfLine = false;
}
}
}
private static int parseInt(TerminalNode node) {
return parseInt(node.getSymbol());
}
private static int parseInt(Token token) {
String text = token.getText();
int radix = 10;
if (text.startsWith("0x") || text.startsWith("0X")) {
radix = 16;
text = text.substring(2);
}
return Integer.parseInt(text, radix);
}
// endregion
}

Просмотреть файл

@ -1,80 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.common.base.Joiner;
import com.microsoft.thrifty.schema.ErrorReporter;
import com.microsoft.thrifty.schema.Location;
import com.microsoft.thrifty.schema.antlr.AntlrThriftLexer;
import com.microsoft.thrifty.schema.antlr.AntlrThriftParser;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import java.util.Locale;
@SuppressWarnings({"WeakerAccess", "unused"}) // public methods are part of our "official" API surface
public final class ThriftParser {
/**
* Parse the given Thrift {@code text}, using the given {@code location}
* to anchor parsed elements withing the file.
* @param location the {@link Location} of the data being parsed.
* @param text the text to be parsed.
* @return a representation of the parsed Thrift data.
*/
public static ThriftFileElement parse(Location location, String text) {
return parse(location, text, new ErrorReporter());
}
/**
* Parse the given Thrift {@code text}, using the given {@code location}
* to anchor parsed elements withing the file.
* @param location the {@link Location} of the data being parsed.
* @param text the text to be parsed.
* @param reporter an {@link ErrorReporter} to collect warnings.
* @return a representation of the parsed Thrift data.
*/
public static ThriftFileElement parse(Location location, String text, ErrorReporter reporter) {
CodePointCharStream charStream = CharStreams.fromString(text, location.path());
AntlrThriftLexer lexer = new AntlrThriftLexer(charStream);
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
AntlrThriftParser antlrParser = new AntlrThriftParser(tokenStream);
ThriftListener thriftListener = new ThriftListener(tokenStream, reporter, location);
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk(thriftListener, antlrParser.document());
if (reporter.hasError()) {
String errorReports = Joiner.on('\n').join(reporter.formattedReports());
String message = String.format(Locale.US, "Syntax errors in %s:\n%s", location, errorReports);
throw new IllegalStateException(message);
}
return thriftListener.buildFileElement();
}
private ThriftParser() {
// no instances
}
}

Просмотреть файл

@ -1,72 +0,0 @@
package com.microsoft.thrifty.schema.parser;
import java.util.UUID;
/**
* Utility class to inject handlers to certain standard Thrifty operations.
*/
public final class ThriftyParserPlugins {
private static final UUIDProvider DEFAULT_UUID_PROVIDER = UUID::randomUUID;
private static volatile UUIDProvider uuidProvider = DEFAULT_UUID_PROVIDER;
/**
* Prevents changing the plugins.
*/
private static volatile boolean lockdown;
/**
* Prevents changing the plugins from then on.
* <p>
* This allows container-like environments to prevent client messing with plugins.
*/
public static void lockdown() {
lockdown = true;
}
/**
* Returns true if the plugins were locked down.
*
* @return true if the plugins were locked down
*/
public static boolean isLockdown() {
return lockdown;
}
/**
* @param uuidProvider the provider to use for generating {@link UUID}s for elements.
*/
public static void setUUIDProvider(UUIDProvider uuidProvider) {
if (lockdown) {
throw new IllegalStateException("Plugins can't be changed anymore");
}
ThriftyParserPlugins.uuidProvider = uuidProvider;
}
/**
* @return a {@link UUID} as dictated by {@link #uuidProvider}. Default is random UUIDs.
*/
public static UUID createUUID() {
return uuidProvider.call();
}
public static void reset() {
uuidProvider = DEFAULT_UUID_PROVIDER;
}
private ThriftyParserPlugins() {
// No instances.
}
/**
* A simple provider interface for creating {@link UUID}s.
*/
public interface UUIDProvider {
/**
* @return a {@link UUID}.
*/
UUID call();
}
}

Просмотреть файл

@ -1,59 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.microsoft.thrifty.schema.Location;
import javax.annotation.Nullable;
public abstract class TypeElement {
public abstract Location location();
public abstract String name();
@Nullable
public abstract AnnotationElement annotations();
public static TypeElement scalar(Location location, String name, AnnotationElement annotations) {
return ScalarTypeElement.create(location, name, annotations);
}
public static TypeElement list(
Location location,
TypeElement elementType,
AnnotationElement annotations) {
return ListTypeElement.create(location, elementType, annotations);
}
public static TypeElement set(
Location location,
TypeElement elementType,
AnnotationElement annotations) {
return SetTypeElement.create(location, elementType, annotations);
}
public static TypeElement map(
Location location,
TypeElement key,
TypeElement value,
AnnotationElement annotations) {
return MapTypeElement.create(location, key, value, annotations);
}
}

Просмотреть файл

@ -1,61 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser;
import com.google.auto.value.AutoValue;
import com.microsoft.thrifty.schema.Location;
import java.util.UUID;
import javax.annotation.Nullable;
@AutoValue
public abstract class TypedefElement {
public abstract Location location();
public abstract String documentation();
public abstract TypeElement oldType();
public abstract String newName();
public abstract UUID uuid();
@Nullable
public abstract AnnotationElement annotations();
TypedefElement() { }
public static Builder builder(Location location) {
return new AutoValue_TypedefElement.Builder()
.location(location)
.documentation("")
.uuid(ThriftyParserPlugins.createUUID());
}
@AutoValue.Builder
public interface Builder {
Builder location(Location location);
Builder documentation(String documentation);
Builder oldType(TypeElement oldType);
Builder newName(String newName);
Builder uuid(UUID uuid);
Builder annotations(AnnotationElement annotations);
TypedefElement build();
}
}

Просмотреть файл

@ -1,28 +0,0 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
/**
* Contains a Thrift parser implementation.
*
* The output of the parser is an untyped AST - types are not
* validated, typedefs are unresolved, etc.
*/
package com.microsoft.thrifty.schema.parser;

Просмотреть файл

@ -0,0 +1,185 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser
import com.microsoft.thrifty.schema.Location
/**
* Represents a literal value in a Thrift file for a constant or a field's
* default value.
*/
// TODO: Convert this to a sealed class
data class ConstValueElement(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The kind of constant value this is - integer, real, list, identifier, etc.
*/
@get:JvmName("kind")
val kind: Kind,
/**
* The actual Thrift text comprising this const value.
*/
@get:JvmName("thriftText")
val thriftText: String,
/**
* The parsed value itself. Will be of a type dictated by the value's [kind].
*/
@get:JvmName("value")
val value: Any
) {
@get:JvmName("isInt")
val isInt: Boolean
get() = kind == Kind.INTEGER
@get:JvmName("isDouble")
val isDouble: Boolean
get() = kind == Kind.DOUBLE
@get:JvmName("isString")
val isString: Boolean
get() = kind == Kind.STRING
@get:JvmName("isIdentifier")
val isIdentifier: Boolean
get() = kind == Kind.IDENTIFIER
@get:JvmName("isList")
val isList: Boolean
get() = kind == Kind.LIST
@get:JvmName("isMap")
val isMap: Boolean
get() = kind == Kind.MAP
fun getAsInt(): Int {
check(isInt) { "Cannot convert to int; kind=$kind" }
return (value as Long).toInt()
}
fun getAsLong(): Long {
check(isInt) { "Cannot convert to long; kind=$kind" }
return value as Long
}
fun getAsDouble(): Double {
check(isDouble) { "Cannot convert to double; kind=$kind" }
return value as Double
}
fun getAsString(): String {
check(isString || isIdentifier) { "Cannot convert to string; kind=$kind" }
return value as String
}
@Suppress("UNCHECKED_CAST")
fun getAsList(): List<ConstValueElement> {
check(isList) { "Cannot convert to list; kind=$kind"}
return value as List<ConstValueElement>
}
@Suppress("UNCHECKED_CAST")
fun getAsMap(): Map<ConstValueElement, ConstValueElement> {
check(isMap) { "Cannot convert to map; kind=$kind" }
return value as Map<ConstValueElement, ConstValueElement>
}
/**
* Defines the kinds of values representable as a [ConstValueElement].
*/
enum class Kind {
/**
* Ye Olde Integer.
*
* Will actually be a [Long], at runtime.
*/
INTEGER,
/**
* A 64-it floating-point number.
*/
DOUBLE,
/**
* A quoted string.
*/
STRING,
/**
* An unquoted string, naming some other Thrift entity.
*/
IDENTIFIER,
/**
* A list of [ConstValueElements][ConstValueElement]
*
* It is assumed that all values in the list share the same type; this
* is not enforced by the parser.
*/
LIST,
/**
* An key-value mapping of [ConstValueElements][ConstValueElement].
*
* It is assumed that all keys share the same type, and that all values
* also share the same type. This is not enforced by the parser.
*/
MAP,
}
companion object {
@JvmStatic
fun integer(location: Location, text: String, value: Long): ConstValueElement {
return ConstValueElement(location, Kind.INTEGER, text, value)
}
@JvmStatic
fun real(location: Location, text: String, value: Double): ConstValueElement {
return ConstValueElement(location, Kind.DOUBLE, text, value)
}
@JvmStatic
fun literal(location: Location, text: String, value: String): ConstValueElement {
return ConstValueElement(location, Kind.STRING, text, value)
}
@JvmStatic
fun identifier(location: Location, text: String, value: String): ConstValueElement {
return ConstValueElement(location, Kind.IDENTIFIER, text, value)
}
@JvmStatic
fun list(location: Location, text: String, value: List<ConstValueElement>): ConstValueElement {
return ConstValueElement(location, Kind.LIST, text, value.toList())
}
@JvmStatic
fun map(location: Location, text: String, value: Map<ConstValueElement, ConstValueElement>): ConstValueElement {
return ConstValueElement(location, Kind.MAP, text, value.toMap())
}
}
}

Просмотреть файл

@ -0,0 +1,677 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser
import com.microsoft.thrifty.schema.Location
import com.microsoft.thrifty.schema.NamespaceScope
import com.microsoft.thrifty.schema.Requiredness
import java.util.UUID
/**
* Represents an instance of one or more Thrift annotations.
*
* @constructor Creates a new instance of [AnnotationElement].
*/
data class AnnotationElement(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The annotation values.
*/
@get:JvmName("values")
val values: Map<String, String>
) {
/**
* True if this element contains no annotation values, otherwise false.
*/
@get:JvmName("isEmpty")
val isEmpty: Boolean
get() = values.isEmpty()
/**
* The number of annotation values in this element.
*/
@get:JvmName("size")
val size: Int
get() = values.size
/**
* Gets the value of the given annotation [key], if present.
*/
operator fun get(key: String): String? = values[key]
}
/**
* Represents the inclusion of one Thrift program into another.
*/
data class IncludeElement(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* Indicates whether or not this is a `cpp_include` statement.
*/
@get:JvmName("isCpp")
val isCpp: Boolean,
/**
* The path (relative or absolute) of the included program.
*/
@get:JvmName("path")
val path: String
)
/**
* Represents the declaration of a new name for an existing type.
*/
data class TypedefElement @JvmOverloads constructor(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The type receiving a new name.
*/
@get:JvmName("oldType")
val oldType: TypeElement,
/**
* The new name for [oldType].
*/
@get:JvmName("newName")
val newName: String,
/**
* The documentation associated with this element, if any.
*/
@get:JvmName("documentation")
val documentation: String = "",
/**
* A UUID uniquely identifying this element.
*/
@get:JvmName("uuid")
val uuid: UUID = ThriftyParserPlugins.createUUID(),
/**
* The annotations associated with this element, if any.
*/
@get:JvmName("annotations")
val annotations: AnnotationElement? = null
)
/**
* Represents the declaration of a language-specific namespace in a Thrift
* program.
*/
data class NamespaceElement @JvmOverloads constructor(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The language ("scope") to which this declaration applies.
*/
@get:JvmName("scope")
val scope: NamespaceScope,
/**
* The name of the namespace.
*/
@get:JvmName("namespace")
val namespace: String,
/**
* The annotations associated with this element, if any.
*/
@get:JvmName("annotations")
val annotations: AnnotationElement? = null
)
/**
* Represents the declaration of a named constant value in a Thrift program.
*/
data class ConstElement @JvmOverloads constructor(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The type of the constant's value.
*/
@get:JvmName("type")
val type: TypeElement,
/**
* The name of the constant.
*/
@get:JvmName("name")
val name: String,
/**
* The literal value of the constant.
*
* This is not guaranteed by the parser to conform to the declared [type].
*/
@get:JvmName("value")
val value: ConstValueElement,
/**
* The documentation associated with this element, if any.
*/
@get:JvmName("documentation")
val documentation: String = "",
/**
* A UUID uniquely identifying this element.
*/
@get:JvmName("uuid")
val uuid: UUID = ThriftyParserPlugins.createUUID()
)
/**
* Represents a single named member of a Thrift enumeration.
*/
data class EnumMemberElement @JvmOverloads constructor(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The name of the enum member.
*/
@get:JvmName("name")
val name: String,
/**
* The integral value associated with the enum member.
*/
@get:JvmName("value")
val value: Int,
/**
* The documentation associated with this element, if any.
*/
@get:JvmName("documentation")
val documentation: String = "",
/**
* The annotations associated with the enum member, if any.
*/
@get:JvmName("annotations")
val annotations: AnnotationElement? = null,
/**
* A UUID uniquely identifying this element.
*/
@get:JvmName("uuid")
val uuid: UUID = ThriftyParserPlugins.createUUID()
)
/**
* Represents a Thrift enumeration.
*/
data class EnumElement @JvmOverloads constructor(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The name of the enumeration.
*/
@get:JvmName("name")
val name: String,
/**
* The members comprising this enumeration.
*/
@get:JvmName("members")
val members: List<EnumMemberElement>,
/**
* The documentation associated with this enumeration, if any.
*/
@get:JvmName("documentation")
val documentation: String = "",
/**
* The annotations associated with this element, if any.
*/
@get:JvmName("annotations")
val annotations: AnnotationElement? = null,
/**
* A UUID uniquely identifying this element.
*/
@get:JvmName("uuid")
val uuid: UUID = ThriftyParserPlugins.createUUID()
)
/**
* Represents a field in a Thrift struct, union, or exception.
*/
data class FieldElement @JvmOverloads constructor(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The integer ID of the field.
*/
@get:JvmName("fieldId")
val fieldId: Int,
/**
* The type of the field.
*/
@get:JvmName("type")
val type: TypeElement,
/**
* The name of the field.
*/
@get:JvmName("name")
val name: String,
/**
* The [Requiredness] of the field.
*/
@get:JvmName("requiredness")
val requiredness: Requiredness = Requiredness.DEFAULT,
/**
* The documentation associated with the field, if any.
*/
@get:JvmName("documentation")
val documentation: String = "",
/**
* The default value of the field, if any.
*/
@get:JvmName("constValue")
val constValue: ConstValueElement? = null,
/**
* The annotations associated with the field, if any.
*/
@get:JvmName("annotations")
val annotations: AnnotationElement? = null,
/**
* A UUID uniquely identifying this element.
*/
@get:JvmName("uuid")
val uuid: UUID = ThriftyParserPlugins.createUUID()
)
/**
* Represents the definition of a Thrift struct, union, or exception.
*/
data class StructElement @JvmOverloads constructor(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The name of the struct, union, or exception.
*/
@get:JvmName("name")
val name: String,
/**
* The kind of struct represented by this element: struct, union, or exception.
*/
@get:JvmName("type")
val type: Type,
/**
* The fields comprising this struct.
*/
@get:JvmName("fields")
val fields: List<FieldElement>,
/**
* The documentation associated with this struct, if any.
*/
@get:JvmName("documentation")
val documentation: String = "",
/**
* The annotations associated with this element, if any.
*/
@get:JvmName("annotations")
val annotations: AnnotationElement? = null,
/**
* A UUID uniquely identifying this element.
*/
@get:JvmName("uuid")
val uuid: UUID = ThriftyParserPlugins.createUUID()
) {
/**
* Defines the different types of structured element in the Thrift language.
*/
enum class Type {
/**
* A struct, in the C sense of the term. That is, an ordered set of
* named fields having heterogeneous types.
*/
STRUCT,
/**
* A union, also in the C sense of the term. A set of named fields, of
* which at most one may have a value at the same time.
*/
UNION,
/**
* An exception is like a [STRUCT], except that it serves as an error
* type that communicates failure from an RPC call. Declared as part
* of a [FunctionElement].
*/
EXCEPTION
}
}
/**
* Represents an RPC function declaration.
*
* Functions are always declared within a [ServiceElement].
*/
data class FunctionElement @JvmOverloads constructor(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The name of the function.
*/
@get:JvmName("name")
val name: String,
/**
* The return type of the function. May be `void` to indicate no
* return type.
*/
@get:JvmName("returnType")
val returnType: TypeElement,
/**
* A list, possibly empty, of function parameters.
*/
@get:JvmName("params")
val params: List<FieldElement> = emptyList(),
/**
* A list, possibly empty, of exceptions thrown by this function.
*/
@get:JvmName("exceptions")
val exceptions: List<FieldElement> = emptyList(),
/**
* True if the function is `oneway`, otherwise false.
*
* A function declared with the `oneway` keyword has no return type,
* and generated service clients will not wait for a response from
* the remote endpoint when making a one-way RPC call.
*
* One-way functions must have a return type of `void`; this is not
* validated at parse time.
*/
@get:JvmName("oneWay")
val oneWay: Boolean = false,
/**
* The documentation associated with this function, if any.
*/
@get:JvmName("documentation")
val documentation: String = "",
/**
* The annotations associated with this function, if any.
*/
@get:JvmName("annotations")
val annotations: AnnotationElement? = null,
/**
* A UUID uniquely identifying this element.
*/
@get:JvmName("uuid")
val uuid: UUID = ThriftyParserPlugins.createUUID()
)
/**
* Represents the declaration of a Thrift service.
*
* A service is an entity having zero or more functions that can be invoked by
* remote clients. Services can inherit the definition of other services, very
* much like inheritance in an object-oriented language.
*/
data class ServiceElement @JvmOverloads constructor(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The name of the service.
*/
@get:JvmName("name")
val name: String,
/**
* The list, possibly empty, of functions defined by this service.
*/
@get:JvmName("functions")
val functions: List<FunctionElement> = emptyList(),
/**
* The base type, if any, of this service.
*
* A base type is presumed to refer to another service, but that is not
* enforced by the parser.
*/
@get:JvmName("extendsService")
val extendsService: TypeElement? = null,
/**
* The documentation associated with this service, if any.
*/
@get:JvmName("documentation")
val documentation: String = "",
/**
* The annotations associated with this service, if any.
*/
@get:JvmName("annotations")
val annotations: AnnotationElement? = null,
/**
* A UUID uniquely identifying this element.
*/
@get:JvmName("uuid")
val uuid: UUID = ThriftyParserPlugins.createUUID()
)
/**
* Represents a Thrift file, and everything defined within it.
*/
data class ThriftFileElement(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The list of all namespaces defined within this file.
*/
@get:JvmName("namespaces")
val namespaces: List<NamespaceElement> = emptyList(),
/**
* The list of all other thrift files included by this file.
*/
@get:JvmName("includes")
val includes: List<IncludeElement> = emptyList(),
/**
* The list of all constants defined within this file.
*/
@get:JvmName("constants")
val constants: List<ConstElement> = emptyList(),
/**
* The list of all typedefs defined within this file.
*/
@get:JvmName("typedefs")
val typedefs: List<TypedefElement> = emptyList(),
/**
* The list of all enums defined within this file.
*/
@get:JvmName("enums")
val enums: List<EnumElement> = emptyList(),
/**
* The list of all structs defined within this file.
*/
@get:JvmName("structs")
val structs: List<StructElement> = emptyList(),
/**
* The list of all unions defined within this file.
*/
@get:JvmName("unions")
val unions: List<StructElement> = emptyList(),
/**
* The list of all exceptions defined within this file.
*/
@get:JvmName("exceptions")
val exceptions: List<StructElement> = emptyList(),
/**
* The list of all services defined within this file.
*/
@get:JvmName("services")
val services: List<ServiceElement> = emptyList()
) {
class Builder(private val location: Location) {
private var namespaces = emptyList<NamespaceElement>()
private var includes = emptyList<IncludeElement>()
private var constants = emptyList<ConstElement>()
private var typedefs = emptyList<TypedefElement>()
private var enums = emptyList<EnumElement>()
private var structs = emptyList<StructElement>()
private var unions = emptyList<StructElement>()
private var exceptions = emptyList<StructElement>()
private var services: List<ServiceElement> = emptyList()
fun namespaces(namespaces: List<NamespaceElement>): Builder {
this.namespaces = namespaces
return this
}
fun includes(includes: List<IncludeElement>): Builder {
this.includes = includes
return this
}
fun constants(constants: List<ConstElement>): Builder {
this.constants = constants
return this
}
fun typedefs(typedefs: List<TypedefElement>): Builder {
this.typedefs = typedefs
return this
}
fun enums(enums: List<EnumElement>): Builder {
this.enums = enums
return this
}
fun structs(structs: List<StructElement>): Builder {
this.structs = structs
return this
}
fun unions(unions: List<StructElement>): Builder {
this.unions = unions
return this
}
fun exceptions(exceptions: List<StructElement>): Builder {
this.exceptions = exceptions
return this
}
fun services(services: List<ServiceElement>): Builder {
this.services = services
return this
}
fun build(): ThriftFileElement {
return ThriftFileElement(
location = location,
namespaces = namespaces,
includes = includes,
constants = constants,
typedefs = typedefs,
enums = enums,
structs = structs,
unions = unions,
exceptions = exceptions,
services = services
)
}
}
companion object {
@JvmStatic fun builder(location: Location) = Builder(location)
}
}

Просмотреть файл

@ -0,0 +1,781 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.microsoft.thrifty.schema.ErrorReporter
import com.microsoft.thrifty.schema.Location
import com.microsoft.thrifty.schema.NamespaceScope
import com.microsoft.thrifty.schema.Requiredness
import com.microsoft.thrifty.schema.antlr.AntlrThriftBaseListener
import com.microsoft.thrifty.schema.antlr.AntlrThriftLexer
import com.microsoft.thrifty.schema.antlr.AntlrThriftParser
import org.antlr.v4.runtime.CommonTokenStream
import org.antlr.v4.runtime.Lexer
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.Token
import org.antlr.v4.runtime.tree.ErrorNode
import org.antlr.v4.runtime.tree.TerminalNode
import java.util.BitSet
/**
* A set of callbacks that, when used with a [org.antlr.v4.runtime.tree.ParseTreeWalker],
* assemble a [ThriftFileElement] from an [AntlrThriftParser].
*
* Instances of this class are single-use; after walking a parse tree, it will contain
* that parser's state as thrifty-schema parser elements.
*
* @param tokenStream the same token stream used with the corresponding [AntlrThriftParser];
* this stream will be queried for "hidden" tokens containing parsed doc
* comments.
* @param errorReporter an error reporting mechanism, used to communicate errors during parsing.
* @param location a location pointing at the beginning of the file being parsed.
*/
internal class ThriftListener(
private val tokenStream: CommonTokenStream,
private val errorReporter: ErrorReporter,
private val location: Location
) : AntlrThriftBaseListener() {
// We need to record which comment tokens have been treated as trailing documentation,
// so that scanning for leading doc tokens for subsequent elements knows where to stop.
// We can do this with a bitset tracking token indices of trailing-comment tokens.
private val trailingDocTokenIndexes = BitSet(INITIAL_BITSET_CAPACITY)
private val includes = mutableListOf<IncludeElement>()
private val namespaces = mutableListOf<NamespaceElement>()
private val enums = mutableListOf<EnumElement>()
private val typedefs = mutableListOf<TypedefElement>()
private val structs = mutableListOf<StructElement>()
private val unions = mutableListOf<StructElement>()
private val exceptions = mutableListOf<StructElement>()
private val consts = mutableListOf<ConstElement>()
private val services = mutableListOf<ServiceElement>()
fun buildFileElement(): ThriftFileElement {
return ThriftFileElement.builder(location)
.includes(includes)
.namespaces(namespaces)
.typedefs(typedefs)
.enums(enums)
.structs(structs)
.unions(unions)
.exceptions(exceptions)
.constants(consts)
.services(services)
.build()
}
override fun exitInclude(ctx: AntlrThriftParser.IncludeContext) {
val pathNode = ctx.LITERAL()
val path = unquote(locationOf(pathNode), pathNode.text, false)
includes.add(IncludeElement(locationOf(ctx), false, path))
}
override fun exitCppInclude(ctx: AntlrThriftParser.CppIncludeContext) {
val pathNode = ctx.LITERAL()
val path = unquote(locationOf(pathNode), pathNode.text, false)
includes.add(IncludeElement(locationOf(ctx), true, path))
}
override fun exitStandardNamespace(ctx: AntlrThriftParser.StandardNamespaceContext) {
val scopeName = ctx.namespaceScope().text
val name = ctx.ns.text
val annotations = annotationsFromAntlr(ctx.annotationList())
val scope = NamespaceScope.forThriftName(scopeName)
if (scope == null) {
errorReporter.warn(locationOf(ctx.namespaceScope()), "Unknown namespace scope '$scopeName'")
return
}
val element = NamespaceElement(
location = locationOf(ctx),
scope = scope,
namespace = name,
annotations = annotations)
namespaces.add(element)
}
override fun exitPhpNamespace(ctx: AntlrThriftParser.PhpNamespaceContext) {
val element = NamespaceElement(
locationOf(ctx),
scope = NamespaceScope.PHP,
namespace = unquote(locationOf(ctx.LITERAL()), ctx.LITERAL().text),
annotations = annotationsFromAntlr(ctx.annotationList()))
namespaces.add(element)
}
override fun exitXsdNamespace(ctx: AntlrThriftParser.XsdNamespaceContext) {
errorReporter.error(locationOf(ctx), "'xsd_namespace' is unsupported")
}
override fun exitSenum(ctx: AntlrThriftParser.SenumContext) {
errorReporter.error(locationOf(ctx), "'senum' is unsupported; use 'enum' instead")
}
override fun exitEnumDef(ctx: AntlrThriftParser.EnumDefContext) {
val enumName = ctx.IDENTIFIER().text
var nextValue = 0
val values = mutableSetOf<Int>()
val members = ArrayList<EnumMemberElement>(ctx.enumMember().size)
for (memberContext in ctx.enumMember()) {
var value = nextValue
val valueToken = memberContext.INTEGER()
if (valueToken != null) {
value = parseInt(valueToken)
}
if (!values.add(value)) {
errorReporter.error(locationOf(memberContext), "duplicate enum value: $value")
continue
}
nextValue = value + 1
val element = EnumMemberElement(
location = locationOf(memberContext),
name = memberContext.IDENTIFIER().text,
value = value,
documentation = formatJavadoc(memberContext),
annotations = annotationsFromAntlr(memberContext.annotationList()))
members.add(element)
}
val doc = formatJavadoc(ctx)
val element = EnumElement(
location = locationOf(ctx),
name = enumName,
documentation = doc,
annotations = annotationsFromAntlr(ctx.annotationList()),
members = members)
enums.add(element)
}
override fun exitStructDef(ctx: AntlrThriftParser.StructDefContext) {
val name = ctx.IDENTIFIER().text
val fields = parseFieldList(ctx.field())
val element = StructElement(
location = locationOf(ctx),
name = name,
fields = fields,
type = StructElement.Type.STRUCT,
documentation = formatJavadoc(ctx),
annotations = annotationsFromAntlr(ctx.annotationList()))
structs.add(element)
}
override fun exitUnionDef(ctx: AntlrThriftParser.UnionDefContext) {
val name = ctx.IDENTIFIER().text
val fields = parseFieldList(ctx.field())
var numFieldsWithDefaultValues = 0
for (i in fields.indices) {
val element = fields[i]
if (element.requiredness == Requiredness.REQUIRED) {
val fieldContext = ctx.field(i)
errorReporter.error(locationOf(fieldContext), "unions cannot have required fields")
}
if (element.constValue != null) {
++numFieldsWithDefaultValues
}
}
if (numFieldsWithDefaultValues > 1) {
errorReporter.error(locationOf(ctx), "unions can have at most one default value")
}
val element = StructElement(
location = locationOf(ctx),
name = name,
fields = fields,
type = StructElement.Type.UNION,
documentation = formatJavadoc(ctx),
annotations = annotationsFromAntlr(ctx.annotationList()))
unions.add(element)
}
override fun exitExceptionDef(ctx: AntlrThriftParser.ExceptionDefContext) {
val name = ctx.IDENTIFIER().text
val fields = parseFieldList(ctx.field())
val element = StructElement(
location = locationOf(ctx),
name = name,
fields = fields,
type = StructElement.Type.EXCEPTION,
documentation = formatJavadoc(ctx),
annotations = annotationsFromAntlr(ctx.annotationList()))
exceptions.add(element)
}
private fun parseFieldList(
contexts: List<AntlrThriftParser.FieldContext>,
defaultRequiredness: Requiredness = Requiredness.DEFAULT): ImmutableList<FieldElement> {
var builder: ImmutableList.Builder<FieldElement> = ImmutableList.builder()
val ids = mutableSetOf<Int>()
var nextValue = 1
for (fieldContext in contexts) {
val element = parseField(nextValue, fieldContext, defaultRequiredness)
if (element != null) {
builder = builder.add(element)
if (!ids.add(element.fieldId)) {
errorReporter.error(locationOf(fieldContext), "duplicate field ID: ${element.fieldId}")
}
if (element.fieldId <= 0) {
errorReporter.error(locationOf(fieldContext), "field ID must be greater than zero")
}
if (element.fieldId >= nextValue) {
nextValue = element.fieldId + 1
}
} else {
// assert-fail here?
++nextValue // this represents an error condition
}
}
return builder.build()
}
private fun parseField(
defaultValue: Int,
ctx: AntlrThriftParser.FieldContext,
defaultRequiredness: Requiredness): FieldElement? {
val fieldId = ctx.INTEGER()?.let { parseInt(it) } ?: defaultValue
val fieldName = ctx.IDENTIFIER().text
val requiredness = if (ctx.requiredness() != null) {
when {
ctx.requiredness().text == "required" -> Requiredness.REQUIRED
ctx.requiredness().text == "optional" -> Requiredness.OPTIONAL
else -> throw AssertionError("Unexpected requiredness value: " + ctx.requiredness().text)
}
} else {
defaultRequiredness
}
return FieldElement(
location = locationOf(ctx),
documentation = formatJavadoc(ctx),
fieldId = fieldId,
requiredness = requiredness,
type = typeElementOf(ctx.fieldType()),
name = fieldName,
annotations = annotationsFromAntlr(ctx.annotationList()),
constValue = constValueElementOf(ctx.constValue()))
}
override fun exitTypedef(ctx: AntlrThriftParser.TypedefContext) {
val oldType = typeElementOf(ctx.fieldType())
val typedef = TypedefElement(
location = locationOf(ctx),
documentation = formatJavadoc(ctx),
annotations = annotationsFromAntlr(ctx.annotationList()),
oldType = oldType,
newName = ctx.IDENTIFIER().text)
typedefs.add(typedef)
}
override fun exitConstDef(ctx: AntlrThriftParser.ConstDefContext) {
val constValue = constValueElementOf(ctx.constValue())
if (constValue == null) {
errorReporter.error(
locationOf(ctx.constValue()),
"Invalid const value")
return
}
val element = ConstElement(
location = locationOf(ctx),
documentation = formatJavadoc(ctx),
type = typeElementOf(ctx.fieldType()),
name = ctx.IDENTIFIER().text,
value = constValue)
consts.add(element)
}
override fun exitServiceDef(ctx: AntlrThriftParser.ServiceDefContext) {
val name = ctx.name.text
val extendsService = if (ctx.superType != null) {
val superType = typeElementOf(ctx.superType)
if (superType !is ScalarTypeElement) {
errorReporter.error(locationOf(ctx), "services cannot extend collections")
return
}
superType
} else {
null
}
val service = ServiceElement(
location = locationOf(ctx),
name = name,
functions = parseFunctionList(ctx.function()),
extendsService = extendsService,
documentation = formatJavadoc(ctx),
annotations = annotationsFromAntlr(ctx.annotationList())
)
services.add(service)
}
private fun parseFunctionList(functionContexts: List<AntlrThriftParser.FunctionContext>): ImmutableList<FunctionElement> {
val functions = ImmutableList.builder<FunctionElement>()
for (ctx in functionContexts) {
val name = ctx.IDENTIFIER().text
val returnType = if (ctx.fieldType() != null) {
typeElementOf(ctx.fieldType())
} else {
val token = ctx.getToken(AntlrThriftLexer.VOID, 0)
?: throw AssertionError("Function has no return type, and no VOID token - grammar error")
val loc = locationOf(token)
// Do people actually annotation 'void'? We'll find out!
TypeElement.scalar(loc, "void", null)
}
val isOneway = ctx.ONEWAY() != null
val maybeThrowsList = ctx.throwsList()?.let {
parseFieldList(it.fieldList().field())
}
val function = FunctionElement(
location = locationOf(ctx),
oneWay = isOneway,
returnType = returnType,
name = name,
documentation = formatJavadoc(ctx),
annotations = annotationsFromAntlr(ctx.annotationList()),
params = parseFieldList(ctx.fieldList().field(), Requiredness.REQUIRED),
exceptions = maybeThrowsList ?: emptyList()
)
functions.add(function)
}
return functions.build()
}
override fun visitErrorNode(node: ErrorNode) {
errorReporter.error(locationOf(node), node.text)
}
// region Utilities
private fun annotationsFromAntlr(ctx: AntlrThriftParser.AnnotationListContext?): AnnotationElement? {
if (ctx == null) {
return null
}
val annotations = mutableMapOf<String, String>()
for (annotationContext in ctx.annotation()) {
val name = annotationContext.IDENTIFIER().text
annotations[name] = if (annotationContext.LITERAL() != null) {
unquote(locationOf(annotationContext.LITERAL()), annotationContext.LITERAL().text)
} else {
"true"
}
}
return AnnotationElement(locationOf(ctx), annotations)
}
private fun locationOf(ctx: ParserRuleContext): Location {
return locationOf(ctx.getStart())
}
private fun locationOf(node: TerminalNode): Location {
return locationOf(node.symbol)
}
private fun locationOf(token: Token): Location {
val line = token.line
val col = token.charPositionInLine + 1 // Location.col is 1-based, Token.col is 0-based
return location.at(line, col)
}
private fun unquote(location: Location, literal: String, processEscapes: Boolean = true): String {
val chars = literal.toCharArray()
val startChar = chars[0]
val endChar = chars[chars.size - 1]
if (startChar != endChar || startChar != '\'' && startChar != '"') {
throw AssertionError("Incorrect UNESCAPED_LITERAL rule: $literal")
}
val sb = StringBuilder(literal.length - 2)
var i = 1
val end = chars.size - 1
while (i < end) {
val c = chars[i++]
if (processEscapes && c == '\\') {
if (i == end) {
errorReporter.error(location, "Unterminated literal")
break
}
val escape = chars[i++]
when (escape) {
'a' -> sb.append(0x7.toChar())
'b' -> sb.append('\b')
'f' -> sb.append('\u000C')
'n' -> sb.append('\n')
'r' -> sb.append('\r')
't' -> sb.append('\t')
'v' -> sb.append(0xB.toChar())
'\\' -> sb.append('\\')
'u' -> throw UnsupportedOperationException("unicode escapes not yet implemented")
else -> if (escape == startChar) {
sb.append(startChar)
} else {
errorReporter.error(location, "invalid escape character: $escape")
}
}
} else {
sb.append(c)
}
}
return sb.toString()
}
private fun typeElementOf(context: AntlrThriftParser.FieldTypeContext): TypeElement {
if (context.baseType() != null) {
if (context.baseType().text == "slist") {
errorReporter.error(locationOf(context), "slist is unsupported; use list<string> instead")
}
return TypeElement.scalar(
locationOf(context),
context.baseType().text,
annotationsFromAntlr(context.annotationList()))
}
if (context.IDENTIFIER() != null) {
return TypeElement.scalar(
locationOf(context),
context.IDENTIFIER().text,
annotationsFromAntlr(context.annotationList()))
}
if (context.containerType() != null) {
val containerContext = context.containerType()
if (containerContext.mapType() != null) {
val keyType = typeElementOf(containerContext.mapType().key)
val valueType = typeElementOf(containerContext.mapType().value)
return TypeElement.map(
locationOf(containerContext.mapType()),
keyType,
valueType,
annotationsFromAntlr(context.annotationList()))
}
if (containerContext.setType() != null) {
return TypeElement.set(
locationOf(containerContext.setType()),
typeElementOf(containerContext.setType().fieldType()),
annotationsFromAntlr(context.annotationList()))
}
if (containerContext.listType() != null) {
return TypeElement.list(
locationOf(containerContext.listType()),
typeElementOf(containerContext.listType().fieldType()),
annotationsFromAntlr(context.annotationList()))
}
throw AssertionError("Unexpected container type - grammar error!")
}
throw AssertionError("Unexpected type - grammar error!")
}
private fun constValueElementOf(ctx: AntlrThriftParser.ConstValueContext?): ConstValueElement? {
if (ctx == null) {
return null
}
if (ctx.INTEGER() != null) {
try {
val value = parseLong(ctx.INTEGER())
return ConstValueElement.integer(locationOf(ctx), ctx.INTEGER().text, value)
} catch (e: NumberFormatException) {
throw AssertionError("Invalid integer accepted by ANTLR grammar: " + ctx.INTEGER().text)
}
}
if (ctx.DOUBLE() != null) {
val text = ctx.DOUBLE().text
try {
val value = java.lang.Double.parseDouble(text)
return ConstValueElement.real(locationOf(ctx), ctx.DOUBLE().text, value)
} catch (e: NumberFormatException) {
throw AssertionError("Invalid double accepted by ANTLR grammar: $text")
}
}
if (ctx.LITERAL() != null) {
val text = unquote(locationOf(ctx.LITERAL() as TerminalNode), ctx.LITERAL().text)
return ConstValueElement.literal(locationOf(ctx), ctx.LITERAL().text, text)
}
if (ctx.IDENTIFIER() != null) {
val id = ctx.IDENTIFIER().text
return ConstValueElement.identifier(locationOf(ctx), ctx.IDENTIFIER().text, id)
}
if (ctx.constList() != null) {
val values = ImmutableList.builder<ConstValueElement>()
for (valueContext in ctx.constList().constValue()) {
values.add(constValueElementOf(valueContext)!!)
}
return ConstValueElement.list(locationOf(ctx), ctx.constList().text, values.build())
}
if (ctx.constMap() != null) {
val values = ImmutableMap.builder<ConstValueElement, ConstValueElement>()
for (entry in ctx.constMap().constMapEntry()) {
val key = constValueElementOf(entry.key)
val value = constValueElementOf(entry.value)
values.put(key!!, value!!)
}
return ConstValueElement.map(locationOf(ctx), ctx.constMap().text, values.build())
}
throw AssertionError("unreachable")
}
private fun formatJavadoc(context: ParserRuleContext): String {
return formatJavadoc(
getLeadingComments(context.getStart()) + getTrailingComments(context.getStop()))
}
private fun getLeadingComments(token: Token): List<Token> {
val hiddenTokens = tokenStream.getHiddenTokensToLeft(token.tokenIndex, Lexer.HIDDEN)
return hiddenTokens?.filter {
it.isComment && !trailingDocTokenIndexes.get(it.tokenIndex)
} ?: emptyList()
}
/**
* Read comments following the given token, until the first newline is encountered.
*
* INVARIANT:
* Assumes that the parse tree is being walked top-down, left to right!
*
* Trailing-doc tokens are marked as such, so that subsequent searches for "leading"
* doc don't grab tokens already used as "trailing" doc. If the walk order is *not*
* top-down, left-to-right, then the assumption underpinning the separation of leading
* and trailing comments is broken.
*
* @param endToken the token from which to search for trailing comment tokens.
* @return a list, possibly empty, of all trailing comment tokens.
*/
private fun getTrailingComments(endToken: Token): List<Token> {
val hiddenTokens = tokenStream.getHiddenTokensToRight(endToken.tokenIndex, Lexer.HIDDEN)
if (hiddenTokens != null && hiddenTokens.isNotEmpty()) {
val maybeTrailingDoc = hiddenTokens.first() // only one trailing comment is possible
if (maybeTrailingDoc.isComment) {
trailingDocTokenIndexes.set(maybeTrailingDoc.tokenIndex)
return listOf<Token>(maybeTrailingDoc)
}
}
return emptyList()
}
companion object {
// A number of tokens that should comfortably accommodate most input files
// without wildly re-allocating. Estimated based on the ClientTestThrift
// and TestThrift files, which contain around ~1200 tokens each.
private const val INITIAL_BITSET_CAPACITY = 2048
}
// endregion
}
private val Token.isComment: Boolean
get() {
return when (this.type) {
AntlrThriftLexer.SLASH_SLASH_COMMENT,
AntlrThriftLexer.HASH_COMMENT,
AntlrThriftLexer.MULTILINE_COMMENT -> true
else -> false
}
}
private fun formatJavadoc(commentTokens: List<Token>): String {
val sb = StringBuilder()
for (token in commentTokens) {
val text = token.text
when (token.type) {
AntlrThriftLexer.SLASH_SLASH_COMMENT -> formatSingleLineComment(sb, text, "//")
AntlrThriftLexer.HASH_COMMENT -> formatSingleLineComment(sb, text, "#")
AntlrThriftLexer.MULTILINE_COMMENT -> formatMultilineComment(sb, text)
else -> {
// wat
val symbolicName = AntlrThriftParser.VOCABULARY.getSymbolicName(token.type)
val literalName = AntlrThriftParser.VOCABULARY.getLiteralName(token.type)
val tokenNumber = "${token.type}"
error("Unexpected comment-token type: ${symbolicName ?: literalName ?: tokenNumber}")
}
}
}
return sb.toString().trim { it <= ' ' }.let { doc ->
if (doc.isNotEmpty() && !doc.endsWith("\n")) {
doc + "\n"
} else {
doc
}
}
}
private fun formatSingleLineComment(sb: StringBuilder, text: String, prefix: String) {
var start = prefix.length
var end = text.length
while (start < end && Character.isWhitespace(text[start])) {
++start
}
while (end > start && Character.isWhitespace(text[end - 1])) {
--end
}
if (start != end) {
sb.append(text.substring(start, end))
}
sb.append("\n")
}
private fun formatMultilineComment(sb: StringBuilder, text: String) {
val chars = text.toCharArray()
var pos = "/*".length
val length = chars.size
var isStartOfLine = true
while (pos + 1 < length) {
val c = chars[pos]
if (c == '*' && chars[pos + 1] == '/') {
sb.append("\n")
return
}
if (c == '\n') {
sb.append(c)
isStartOfLine = true
} else if (!isStartOfLine) {
sb.append(c)
} else if (c == '*') {
// skip a single subsequent space, if it exists
if (chars[pos + 1] == ' ') {
pos += 1
}
isStartOfLine = false
} else if (!Character.isWhitespace(c)) {
sb.append(c)
isStartOfLine = false
}
++pos
}
}
private fun parseInt(node: TerminalNode): Int {
return parseInt(node.symbol)
}
private fun parseInt(token: Token): Int {
var text = token.text
var radix = 10
if (text.startsWith("0x") || text.startsWith("0X")) {
radix = 16
text = text.substring(2)
}
return Integer.parseInt(text, radix)
}
private fun parseLong(node: TerminalNode): Long = parseLong(node.symbol)
private fun parseLong(token: Token): Long {
val text: String
val radix: Int
if (token.text.startsWith("0x", ignoreCase = true)) {
text = token.text.substring(2)
radix = 16
} else {
text = token.text
radix = 10
}
return java.lang.Long.parseLong(text, radix)
}

Просмотреть файл

@ -0,0 +1,65 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser
import com.google.common.base.Joiner
import com.microsoft.thrifty.schema.ErrorReporter
import com.microsoft.thrifty.schema.Location
import com.microsoft.thrifty.schema.antlr.AntlrThriftLexer
import com.microsoft.thrifty.schema.antlr.AntlrThriftParser
import org.antlr.v4.runtime.CharStreams
import org.antlr.v4.runtime.CommonTokenStream
import org.antlr.v4.runtime.tree.ParseTreeWalker
import java.util.Locale
object ThriftParser {
/**
* Parse the given Thrift [text], using the given [location] to anchor
* parsed elements within the file.
*
* @param location the [Location] of the file containing the given [text].
* @param text the Thrift text to be parsed.
* @param reporter an [ErrorReporter] to which parse errors will be reported.
* @return a [ThriftFileElement] containing all Thrift elements in the input text.
*/
@JvmStatic
@JvmOverloads
fun parse(location: Location, text: String, reporter: ErrorReporter = ErrorReporter()): ThriftFileElement {
val charStream = CharStreams.fromString(text, location.path())
val lexer = AntlrThriftLexer(charStream)
val tokenStream = CommonTokenStream(lexer)
val antlrParser = AntlrThriftParser(tokenStream)
val documentParseTree = antlrParser.document()
val thriftListener = ThriftListener(tokenStream, reporter, location)
ParseTreeWalker.DEFAULT.walk(thriftListener, documentParseTree)
if (reporter.hasError()) {
val errorReports = Joiner.on('\n').join(reporter.formattedReports())
val message = String.format(Locale.US, "Syntax errors in %s:\n%s", location, errorReports)
throw IllegalStateException(message)
}
return thriftListener.buildFileElement()
}
}

Просмотреть файл

@ -0,0 +1,89 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser
import java.util.UUID
object ThriftyParserPlugins {
private val DEFAULT_UUID_PROVIDER: UUIDProvider = object : UUIDProvider {
override fun call(): UUID = UUID.randomUUID()
}
@Volatile
private var uuidProvider = DEFAULT_UUID_PROVIDER
/**
* Prevents changing the plugins.
*/
@Volatile
private var lockdown: Boolean = false
/**
* Prevents changing the plugins from then on.
*
*
* This allows container-like environments to prevent client messing with plugins.
*/
@JvmStatic fun lockdown() {
lockdown = true
}
/**
* Returns true if the plugins were locked down.
*
* @return true if the plugins were locked down
*/
@JvmStatic fun isLockdown(): Boolean {
return lockdown
}
/**
* @param uuidProvider the provider to use for generating [UUID]s for elements.
*/
@JvmStatic fun setUUIDProvider(uuidProvider: UUIDProvider) {
if (lockdown) {
throw IllegalStateException("Plugins can't be changed anymore")
}
ThriftyParserPlugins.uuidProvider = uuidProvider
}
/**
* @return a [UUID] as dictated by [uuidProvider]. Default is random UUIDs.
*/
@JvmStatic fun createUUID(): UUID {
return uuidProvider.call()
}
@JvmStatic fun reset() {
uuidProvider = DEFAULT_UUID_PROVIDER
}
/**
* A simple provider interface for creating [UUID]s.
*/
interface UUIDProvider {
/**
* @return a [UUID].
*/
fun call(): UUID
}
}

Просмотреть файл

@ -0,0 +1,250 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.schema.parser
import com.microsoft.thrifty.schema.Location
/**
* Represents a reference to a named type.
*
* Types can be scalar (numbers, strings, identifiers, etc) or collections.
*/
sealed class TypeElement(
/**
* The location of the text corresponding to this element.
*/
@get:JvmName("location")
val location: Location,
/**
* The name of the type referenced by this element.
*/
@get:JvmName("name")
val name: String,
/**
* The annotations associated with this type reference, if any.
*/
@get:JvmName("annotations")
val annotations: AnnotationElement? = null
) {
companion object {
/**
* Creates and returns a scalar [TypeElement].
*
* @param location the location of the text corresponding to the scalar.
* @param name the name of the scalar-type reference.
* @param annotations the annotations associated with the reference, if any.
* @return a [ScalarTypeElement].
*/
@JvmStatic
fun scalar(location: Location, name: String, annotations: AnnotationElement? = null) =
ScalarTypeElement(location, name, annotations)
/**
* Creates and returns a list [TypeElement].
*
* @param location the location of the text corresponding to the reference.
* @param elementType the type of element contained by the list type.
* @param annotations the annotations associated with the reference, if any.
* @return a [ListTypeElement].
*/
@JvmStatic
fun list(location: Location, elementType: TypeElement, annotations: AnnotationElement? = null) =
ListTypeElement(elementType, location, annotations)
/**
* Creates and returns a set [TypeElement].
*
* @param location the location of the text corresponding to the reference.
* @param elementType the type of element contained by the set type.
* @param annotations the annotations associated with the reference, if any.
* @return a [SetTypeElement].
*/
@JvmStatic
fun set(location: Location, elementType: TypeElement, annotations: AnnotationElement? = null) =
SetTypeElement(elementType, location, annotations)
/**
* Creates and returns a map [TypeElement].
*
* @param location the location of the text corresponding to the reference.
* @param keyType the ley-type of the map.
* @param valueType the value-type of the map.
* @param annotations the annotations associated with the reference, if any.
* @return a [MapTypeElement].
*/
@JvmStatic
fun map(location: Location,
keyType: TypeElement,
valueType: TypeElement,
annotations: AnnotationElement? = null) = MapTypeElement(keyType, valueType, location, annotations)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TypeElement
if (location != other.location) return false
if (name != other.name) return false
if (annotations != other.annotations) return false
return true
}
override fun hashCode(): Int {
var result = location.hashCode()
result = 31 * result + name.hashCode()
result = 31 * result + (annotations?.hashCode() ?: 0)
return result
}
}
/**
* Represents a reference to a scalar type.
*/
class ScalarTypeElement(
location: Location,
name: String,
annotations: AnnotationElement?
) : TypeElement(location, name, annotations) {
override fun toString(): String {
return "ScalarTypeElement{location=$location, name=$name, annotations=$annotations}"
}
}
/**
* Represents a reference to a set-type.
*/
class SetTypeElement(
/**
* A reference to the type of element contained within this set type.
*/
@get:JvmName("elementType")
val elementType: TypeElement,
location: Location,
annotations: AnnotationElement? = null
) : TypeElement(location, "set<${elementType.name}>", annotations) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is SetTypeElement) return false
if (!super.equals(other)) return false
if (elementType != other.elementType) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + elementType.hashCode()
return result
}
override fun toString(): String {
return "SetTypeElement{location=$location, name=$name, annotations=$annotations, elementType=$elementType}"
}
}
/**
* Represents a reference to a list-type.
*/
class ListTypeElement(
/**
* A reference to the type of element contained within this list type.
*/
@get:JvmName("elementType")
val elementType: TypeElement,
location: Location,
annotations: AnnotationElement? = null
) : TypeElement(location, "list<${elementType.name}>", annotations) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ListTypeElement) return false
if (!super.equals(other)) return false
if (elementType != other.elementType) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + elementType.hashCode()
return result
}
override fun toString(): String {
return "ListTypeElement{location=$location, name=$name, annotations=$annotations, elementType=$elementType}"
}
}
/**
* Represents a reference to a map-type.
*
* A map-type is a typical key-value lookup, a.k.a. associative array,
* dictionary, etc.
*/
class MapTypeElement(
/**
* A reference to the type serving as the key-type of this map type.
*/
@get:JvmName("keyType")
val keyType: TypeElement,
/**
* A reference to the type serving as the value-type of this map type.
*/
@get:JvmName("valueType")
val valueType: TypeElement,
location: Location,
annotations: AnnotationElement? = null
) : TypeElement(location, "map<${keyType.name}, ${valueType.name}>", annotations) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is MapTypeElement) return false
if (!super.equals(other)) return false
if (keyType != other.keyType) return false
if (valueType != other.valueType) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + keyType.hashCode()
result = 31 * result + valueType.hashCode()
return result
}
override fun toString(): String {
return "MapTypeElement{location=$location, name=$name, annotations=$annotations, keyType=$keyType, valueType=$valueType}"
}
}

Просмотреть файл

@ -171,7 +171,7 @@ public class ConstantTest {
@Test
public void enumWithMember() {
ImmutableList.Builder<EnumMember> members = ImmutableList.builder();
members.add(new EnumMember(EnumMemberElement.builder(loc).name("TEST").value(1).build()));
members.add(new EnumMember(new EnumMemberElement(loc, "TEST", 1)));
EnumType et = mock(EnumType.class);
when(et.name()).thenReturn("TestEnum");
@ -185,7 +185,7 @@ public class ConstantTest {
@Test
public void enumWithNonMemberIdentifier() {
ImmutableList.Builder<EnumMember> members = ImmutableList.builder();
members.add(new EnumMember(EnumMemberElement.builder(loc).name("TEST").value(1).build()));
members.add(new EnumMember(new EnumMemberElement(loc, "TEST", 1)));
EnumType et = mock(EnumType.class);
when(et.name()).thenReturn("TestEnum");
@ -206,7 +206,7 @@ public class ConstantTest {
@Test
public void unqualifiedEnumMember() {
ImmutableList.Builder<EnumMember> members = ImmutableList.builder();
members.add(new EnumMember(EnumMemberElement.builder(loc).name("TEST").value(1).build()));
members.add(new EnumMember(new EnumMemberElement(loc, "TEST", 1)));
EnumType et = mock(EnumType.class);
when(et.name()).thenReturn("TestEnum");
@ -252,7 +252,7 @@ public class ConstantTest {
@Test
public void typedefOfEnum() {
EnumMember member = new EnumMember(EnumMemberElement.builder(loc).name("FOO").value(1).build());
EnumMember member = new EnumMember(new EnumMemberElement(loc, "FOO", 1));
ImmutableList<EnumMember> members = ImmutableList.of(member);
EnumType et = mock(EnumType.class);
@ -273,7 +273,7 @@ public class ConstantTest {
@Test
public void typedefOfWrongEnum() {
EnumMember member = new EnumMember(EnumMemberElement.builder(loc).name("FOO").value(1).build());
EnumMember member = new EnumMember(new EnumMemberElement(loc, "FOO", 1));
ImmutableList<EnumMember> members = ImmutableList.of(member);
EnumType et = mock(EnumType.class);
@ -282,7 +282,7 @@ public class ConstantTest {
when(et.getTrueType()).thenReturn(et);
when(et.isEnum()).thenReturn(true);
EnumMember wrongMember = new EnumMember(EnumMemberElement.builder(loc).name("BAR").value(2).build());
EnumMember wrongMember = new EnumMember(new EnumMemberElement(loc, "BAR", 2));
EnumType wt = mock(EnumType.class);
when(wt.name()).thenReturn("DifferentEnum");

Просмотреть файл

@ -15,8 +15,6 @@ import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class EnumTypeTest {
@Mock EnumElement element;
@Mock ThriftType type;
@Mock Program program;
ImmutableMap<NamespaceScope, String> namespaces;
@ -27,19 +25,14 @@ public class EnumTypeTest {
namespaces = ImmutableMap.of();
when(program.namespaces()).thenReturn(namespaces);
when(element.name()).thenReturn("name");
when(element.members()).thenReturn(ImmutableList.<EnumMemberElement>of());
Location location = Location.get("", "");
EnumMemberElement memberElement = EnumMemberElement.builder(location)
.name("FOO")
.value(1)
.build();
EnumMemberElement memberElement = new EnumMemberElement(location, "FOO", 1);
EnumElement element = EnumElement.builder(location)
.name("AnEnum")
.members(ImmutableList.of(memberElement))
.build();
EnumElement element = new EnumElement(
location,
"AnEnum",
ImmutableList.of(memberElement));
enumType = new EnumType(program, element);
}

Просмотреть файл

@ -24,6 +24,7 @@ import com.google.common.collect.ImmutableMap;
import com.microsoft.thrifty.schema.parser.AnnotationElement;
import com.microsoft.thrifty.schema.parser.FieldElement;
import com.microsoft.thrifty.schema.parser.TypeElement;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@ -35,11 +36,30 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class FieldTest {
private Location location;
private int fieldId;
private String fieldName;
private TypeElement fieldType;
private Requiredness requiredness;
private AnnotationElement annotations;
private String documentation;
@Before
public void setup() {
location = Location.get("", "");
fieldId = 1;
fieldName = "foo";
fieldType = TypeElement.scalar(location, "i32", null);
requiredness = Requiredness.DEFAULT;
annotations = null;
documentation = "";
}
@Test
public void requiredFields() {
FieldElement element = fieldBuilder()
.requiredness(Requiredness.REQUIRED)
.build();
requiredness = Requiredness.REQUIRED;
FieldElement element = field();
Field field = new Field(element);
assertTrue(field.required());
assertFalse(field.optional());
@ -47,9 +67,8 @@ public class FieldTest {
@Test
public void optionalFields() {
FieldElement element = fieldBuilder()
.requiredness(Requiredness.OPTIONAL)
.build();
requiredness = Requiredness.OPTIONAL;
FieldElement element = field();
Field field = new Field(element);
assertFalse(field.required());
assertTrue(field.optional());
@ -57,9 +76,7 @@ public class FieldTest {
@Test
public void defaultFields() {
FieldElement element = fieldBuilder()
.requiredness(Requiredness.DEFAULT)
.build();
FieldElement element = field();
Field field = new Field(element);
assertFalse(field.required());
assertFalse(field.optional());
@ -67,7 +84,7 @@ public class FieldTest {
@Test
public void unredactedAndUnobfuscatedByDefault() {
FieldElement element = fieldBuilder().build();
FieldElement element = field();
Field field = new Field(element);
assertFalse(field.isRedacted());
assertFalse(field.isObfuscated());
@ -75,9 +92,8 @@ public class FieldTest {
@Test
public void redactedByThriftAnnotation() {
FieldElement element = fieldBuilder()
.annotations(annotation("thrifty.redacted"))
.build();
annotations = annotation("thrifty.redacted");
FieldElement element = field();
Field field = new Field(element);
assertTrue(field.isRedacted());
@ -85,9 +101,8 @@ public class FieldTest {
@Test
public void redactedByShortThriftAnnotation() {
FieldElement element = fieldBuilder()
.annotations(annotation("redacted"))
.build();
annotations = annotation("redacted");
FieldElement element = field();
Field field = new Field(element);
assertTrue(field.isRedacted());
@ -95,9 +110,8 @@ public class FieldTest {
@Test
public void redactedByJavadocAnnotation() {
FieldElement element = fieldBuilder()
.documentation("/** @redacted */")
.build();
documentation = "/** @redacted */";
FieldElement element = field();
Field field = new Field(element);
assertTrue(field.isRedacted());
@ -105,9 +119,8 @@ public class FieldTest {
@Test
public void obfuscatedByThriftAnnotation() {
FieldElement element = fieldBuilder()
.annotations(annotation("thrifty.obfuscated"))
.build();
annotations = annotation("thrifty.obfuscated");
FieldElement element = field();
Field field = new Field(element);
assertTrue(field.isObfuscated());
@ -115,9 +128,8 @@ public class FieldTest {
@Test
public void obfuscatedByShortThriftAnnotation() {
FieldElement element = fieldBuilder()
.annotations(annotation("obfuscated"))
.build();
annotations = annotation("obfuscated");
FieldElement element = field();
Field field = new Field(element);
assertTrue(field.isObfuscated());
@ -125,9 +137,8 @@ public class FieldTest {
@Test
public void obfuscatedByJavadocAnnotation() {
FieldElement element = fieldBuilder()
.documentation("/** @obfuscated */")
.build();
documentation = "/** @obfuscated */";
FieldElement element = field();
Field field = new Field(element);
assertTrue(field.isObfuscated());
@ -135,7 +146,7 @@ public class FieldTest {
@Test
public void builderCreatesCorrectField() {
FieldElement fieldElement = mock(FieldElement.class);
FieldElement fieldElement = field();
Field field = new Field(fieldElement);
ImmutableMap<String, String> annotations = ImmutableMap.of();
@ -160,14 +171,18 @@ public class FieldTest {
}
private AnnotationElement annotation(String name) {
return AnnotationElement.create(Location.get("", ""), Collections.singletonMap(name, "true"));
return new AnnotationElement(Location.get("", ""), Collections.singletonMap(name, "true"));
}
private FieldElement.Builder fieldBuilder() {
Location location = Location.get("", "");
return FieldElement.builder(location)
.fieldId(1)
.name("foo")
.type(TypeElement.scalar(location, "i32", null));
private FieldElement field() {
return new FieldElement(
location,
fieldId,
fieldType,
fieldName,
requiredness,
documentation,
null, // const value
annotations);
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу