//
// Copyright © 2014, David Tesler (https://github.com/protobufel)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the <organization> nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
package com.github.protobufel.grammar;
import static com.github.protobufel.grammar.ParserUtils.getTotalFieldCount;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.TerminalNode;
import com.github.protobufel.grammar.ParserUtils.CommonTokenStreamEx;
import com.github.protobufel.grammar.ProtoParser.CustomOptionContext;
import com.github.protobufel.grammar.ProtoParser.CustomOptionNamePartContext;
import com.github.protobufel.grammar.ProtoParser.CustomOptionValueContext;
import com.github.protobufel.grammar.ProtoParser.EnumStatementContext;
import com.github.protobufel.grammar.ProtoParser.FileOptionContext;
import com.github.protobufel.grammar.ProtoParser.OptionScalarValueContext;
import com.github.protobufel.grammar.ProtoParser.PackageStatementContext;
import com.github.protobufel.grammar.ProtoParser.ProtoContext;
import com.github.protobufel.grammar.ProtoParser.PublicImportContext;
import com.github.protobufel.grammar.ProtoParser.RegularImportContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionCcGenericServicesContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionGoPackageContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaGenerateEqualsAndHashContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaGenericServicesContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaMultipleFilesContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaOuterClassnameContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaPackageContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionOptimizeForContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionPyGenericServicesContext;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileOptions;
import com.google.protobuf.DescriptorProtos.UninterpretedOption;
/**
* A parser from .proto into FileDescriptorProto with {@link DescriptorProtos.SourceCodeInfo}.
*
* @author protobufel@gmail.com David Tesler
*/
// FIXME fully implement, refactor, and enable!
class SourceInfoProtoFileParser extends ProtoFileParser {
private static boolean treatStandardOptionAsUninterpreted = true;
private final LocationBuilder locationBuilder;
private FileOptions.Builder fileOptionsBuilder; // FIXME add just to compile this file
public SourceInfoProtoFileParser(final CommonTokenStreamEx tokens, final String protoName) {
super(protoName);
locationBuilder = new LocationBuilder(fileBuilder.getSourceCodeInfoBuilder(), tokens);
}
public static boolean isTreatStandardOptionAsUninterpreted() {
return treatStandardOptionAsUninterpreted;
}
public static void setTreatStandardOptionAsUninterpreted(
final boolean treatStandardOptionAsUninterpreted) {
SourceInfoProtoFileParser.treatStandardOptionAsUninterpreted =
treatStandardOptionAsUninterpreted;
}
@Override
public ParsedContext getParsed() {
// return fileBuilder;
return null;
}
@Override
public void exitProto(final ProtoContext ctx) {
super.exitProto(ctx);
locationBuilder.addLocation(0).comments(ctx).setAllSpan(ctx);
}
@Override
public void exitRegularImport(final RegularImportContext ctx) {
super.exitRegularImport(ctx);
// locationBuilder.addLocation()
// .comments(ctx)
// .setAllSpan(ctx.importPath())
// .addPath(FileDescriptorProto.DEPENDENCY_FIELD_NUMBER)
// .addPath(fileBuilder.getDependencyCount() - 1);
locationBuilder.addLocationForPrimitive(FileDescriptorProto.DEPENDENCY_FIELD_NUMBER)
.comments(ctx).setAllSpan(ctx.importPath());
}
@Override
public void exitPublicImport(final PublicImportContext ctx) {
super.exitPublicImport(ctx);
// locationBuilder.addLocation()
// .setAllSpan(ctx.Public())
// .addPath(FileDescriptorProto.PUBLIC_DEPENDENCY_FIELD_NUMBER)
// .addPath(fileBuilder.getPublicDependencyCount() - 1)
//
// .addLocation()
// .comments(ctx)
// .setAllSpan(ctx.importPath())
// .addPath(FileDescriptorProto.DEPENDENCY_FIELD_NUMBER)
// .addPath(fileBuilder.getDependencyCount() - 1);
locationBuilder.addLocationForPrimitive(FileDescriptorProto.PUBLIC_DEPENDENCY_FIELD_NUMBER)
.setAllSpan((TerminalNode) ctx.getChild(1))
.addLocationForPrimitive(FileDescriptorProto.DEPENDENCY_FIELD_NUMBER).comments(ctx)
.setAllSpan(ctx.importPath());
}
@Override
public void exitPackageStatement(final PackageStatementContext ctx) {
super.exitPackageStatement(ctx);
// locationBuilder.addLocation()
// .comments(ctx)
// .setAllSpan(ctx.packageName())
// .addPath(FileDescriptorProto.PACKAGE_FIELD_NUMBER);
locationBuilder.addLocationForPrimitive(FileDescriptorProto.PACKAGE_FIELD_NUMBER).comments(ctx)
.setAllSpan(ctx.packageName());
}
@Override
public void exitStandardFileOptionJavaPackage(final StandardFileOptionJavaPackageContext ctx) {
super.exitStandardFileOptionJavaPackage(ctx);
doStandardOptionSource(ctx, UninterpretedOption.STRING_VALUE_FIELD_NUMBER);
}
@Override
public void exitStandardFileOptionGoPackage(final StandardFileOptionGoPackageContext ctx) {
super.exitStandardFileOptionGoPackage(ctx);
doStandardOptionSource(ctx, UninterpretedOption.STRING_VALUE_FIELD_NUMBER);
}
@Override
public void exitStandardFileOptionJavaMultipleFiles(
final StandardFileOptionJavaMultipleFilesContext ctx) {
super.exitStandardFileOptionJavaMultipleFiles(ctx);
doStandardOptionSource(ctx, UninterpretedOption.IDENTIFIER_VALUE_FIELD_NUMBER);
}
@Override
public void exitStandardFileOptionJavaOuterClassname(
final StandardFileOptionJavaOuterClassnameContext ctx) {
super.exitStandardFileOptionJavaOuterClassname(ctx);
doStandardOptionSource(ctx, UninterpretedOption.STRING_VALUE_FIELD_NUMBER);
}
@Override
public void exitStandardFileOptionCcGenericServices(
final StandardFileOptionCcGenericServicesContext ctx) {
super.exitStandardFileOptionCcGenericServices(ctx);
doStandardOptionSource(ctx, UninterpretedOption.IDENTIFIER_VALUE_FIELD_NUMBER);
}
@Override
public void exitStandardFileOptionPyGenericServices(
final StandardFileOptionPyGenericServicesContext ctx) {
super.exitStandardFileOptionPyGenericServices(ctx);
doStandardOptionSource(ctx, UninterpretedOption.IDENTIFIER_VALUE_FIELD_NUMBER);
}
@Override
public void exitStandardFileOptionJavaGenericServices(
final StandardFileOptionJavaGenericServicesContext ctx) {
super.exitStandardFileOptionJavaGenericServices(ctx);
doStandardOptionSource(ctx, UninterpretedOption.IDENTIFIER_VALUE_FIELD_NUMBER);
}
@Override
public void exitStandardFileOptionJavaGenerateEqualsAndHash(
final StandardFileOptionJavaGenerateEqualsAndHashContext ctx) {
super.exitStandardFileOptionJavaGenerateEqualsAndHash(ctx);
doStandardOptionSource(ctx, UninterpretedOption.IDENTIFIER_VALUE_FIELD_NUMBER);
}
@Override
public void exitStandardFileOptionOptimizeFor(final StandardFileOptionOptimizeForContext ctx) {
super.exitStandardFileOptionOptimizeFor(ctx);
doStandardOptionSource(ctx, UninterpretedOption.IDENTIFIER_VALUE_FIELD_NUMBER);
}
@Override
public void exitFileOption(final FileOptionContext ctx) {
super.exitFileOption(ctx);
if (ctx.customFileOption() != null) {
final CustomOptionContext customOptionCtx = ctx.customFileOption().customOption();
locationBuilder.addLocation().setAllSpan(ctx)
.addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER);
final int optionIndex =
(treatStandardOptionAsUninterpreted ? getTotalFieldCount(fileOptionsBuilder)
: fileOptionsBuilder.getUninterpretedOptionCount()) - 1;
locationBuilder.addLocationClone().addPath(FileOptions.UNINTERPRETED_OPTION_FIELD_NUMBER)
.addPath(optionIndex);
locationBuilder.addLocationClone().clearComments()
.addPath(UninterpretedOption.NAME_FIELD_NUMBER)
.setAllSpan(customOptionCtx.customOptionName());
int i = -1;
for (final CustomOptionNamePartContext namePart : customOptionCtx.customOptionName()
.customOptionNamePart()) {
locationBuilder.addLocation().addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER)
.addPath(FileOptions.UNINTERPRETED_OPTION_FIELD_NUMBER).addPath(optionIndex)
.addPath(UninterpretedOption.NAME_FIELD_NUMBER).addPath(++i).setAllSpan(namePart);
locationBuilder
.addLocationClone()
.addPath(UninterpretedOption.NamePart.NAME_PART_FIELD_NUMBER)
.setAllSpan(
namePart.customOptionNamePartId() == null ? namePart.identifier() : namePart
.customOptionNamePartId());
}
// customOption value locations: can be scalar or aggregate!
final CustomOptionValueContext customOptionValue = customOptionCtx.customOptionValue();
int valuePath;
if (customOptionValue.optionAggregateValue() != null) {
valuePath = UninterpretedOption.AGGREGATE_VALUE_FIELD_NUMBER;
} else {
final OptionScalarValueContext optionScalarValue = customOptionValue.optionScalarValue();
if (optionScalarValue.doubleValue() != null) {
valuePath = UninterpretedOption.DOUBLE_VALUE_FIELD_NUMBER;
} else if (optionScalarValue.identifier() != null) {
valuePath = UninterpretedOption.IDENTIFIER_VALUE_FIELD_NUMBER;
} else if (optionScalarValue.StringLiteral() != null
|| optionScalarValue.BooleanLiteral() != null) {
valuePath = UninterpretedOption.STRING_VALUE_FIELD_NUMBER;
} else if (optionScalarValue.NegativeIntegerLiteral() != null) {
valuePath = UninterpretedOption.NEGATIVE_INT_VALUE_FIELD_NUMBER;
} else if (optionScalarValue.IntegerLiteral() != null) {
valuePath = UninterpretedOption.POSITIVE_INT_VALUE_FIELD_NUMBER;
} else { // we shouldn't arrive here!
throw new RuntimeException("custom option value has unidentified type!");
}
}
locationBuilder.addLocation().addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER)
.addPath(FileOptions.UNINTERPRETED_OPTION_FIELD_NUMBER).addPath(optionIndex)
.addPath(valuePath).setAllSpan(customOptionValue);
// should the aggregate locations be added here? BTW, protoc fails on aggregates!
}
}
private void doStandardOptionSource(final StandardFileOptionContext ctx,
final int customOptionValueType) {
final FileOptionContext parentCtx = (FileOptionContext) ctx.getParent();
locationBuilder.addLocation().setAllSpan(parentCtx)
.addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER);
if (treatStandardOptionAsUninterpreted) {
final int optionIndex = getTotalFieldCount(fileOptionsBuilder) - 1;
locationBuilder.addLocation().comments(parentCtx)
.addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER)
.addPath(FileOptions.UNINTERPRETED_OPTION_FIELD_NUMBER).addPath(optionIndex)
.setAllSpan(parentCtx);
locationBuilder.addLocation().addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER)
.addPath(FileOptions.UNINTERPRETED_OPTION_FIELD_NUMBER).addPath(optionIndex)
.addPath(UninterpretedOption.NAME_FIELD_NUMBER)
.setAllSpan((TerminalNode) ctx.getChild(0));
locationBuilder.addLocation().addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER)
.addPath(FileOptions.UNINTERPRETED_OPTION_FIELD_NUMBER).addPath(optionIndex)
.addPath(UninterpretedOption.NAME_FIELD_NUMBER).addPath(0)
.setAllSpan((TerminalNode) ctx.getChild(0));
locationBuilder.addLocation().addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER)
.addPath(FileOptions.UNINTERPRETED_OPTION_FIELD_NUMBER).addPath(optionIndex)
.addPath(UninterpretedOption.NAME_FIELD_NUMBER).addPath(0)
.addPath(UninterpretedOption.NamePart.NAME_PART_FIELD_NUMBER)
.setAllSpan((TerminalNode) ctx.getChild(0));
locationBuilder.addLocation().addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER)
.addPath(FileOptions.UNINTERPRETED_OPTION_FIELD_NUMBER).addPath(optionIndex)
.addPath(customOptionValueType).setAllSpan(ctx.getChild(2));
} else {
locationBuilder
.addLocation()
.comments(parentCtx)
.addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER)
.addPath(
FileOptions.getDescriptor().findFieldByName(ctx.getChild(0).getText()).getNumber())
.setAllSpan(ctx.getChild(2));
}
}
// ********************* EnumStatement START ****************
@Override
public void enterEnumStatement(final EnumStatementContext ctx) {
super.enterEnumStatement(ctx);
locationBuilder.addEnumLocation().comments(ctx).setAllSpan(ctx)
.addLocationForPrimitive(EnumDescriptorProto.NAME_FIELD_NUMBER).setAllSpan(ctx.identifier());
}
@Override
public void exitEnumStatement(final EnumStatementContext ctx) {
super.exitEnumStatement(ctx);
locationBuilder.popScope();
}
// ***************************** Universal Options treatment START
// *********************
private void doCustomOption(final CustomOptionContext ctx, final ParserRuleContext parentCtx) {
locationBuilder.addLocation().setAllSpan(parentCtx);
locationBuilder.addOptionLocation().setAllSpan(parentCtx).comments(parentCtx)
.addOptionNameLocation().setAllSpan(ctx.customOptionName());
for (final CustomOptionNamePartContext namePart : ctx.customOptionName().customOptionNamePart()) {
locationBuilder
.addLocationForPrimitive(UninterpretedOption.NAME_FIELD_NUMBER)
.setAllSpan(namePart)
.addLocationForPrimitive(UninterpretedOption.NamePart.NAME_PART_FIELD_NUMBER)
.setAllSpan(
namePart.customOptionNamePartId() == null ? namePart.identifier() : namePart
.customOptionNamePartId());
}
// exit OptionName scope
locationBuilder.popScope();
// customOption value locations: can be scalar or aggregate!
final CustomOptionValueContext customOptionValue = ctx.customOptionValue();
int valuePath;
if (customOptionValue.optionAggregateValue() != null) {
valuePath = UninterpretedOption.AGGREGATE_VALUE_FIELD_NUMBER;
} else {
final OptionScalarValueContext optionScalarValue = customOptionValue.optionScalarValue();
if (optionScalarValue.doubleValue() != null) {
valuePath = UninterpretedOption.DOUBLE_VALUE_FIELD_NUMBER;
} else if (optionScalarValue.identifier() != null) {
valuePath = UninterpretedOption.IDENTIFIER_VALUE_FIELD_NUMBER;
} else if (optionScalarValue.StringLiteral() != null
|| optionScalarValue.BooleanLiteral() != null) {
valuePath = UninterpretedOption.STRING_VALUE_FIELD_NUMBER;
} else if (optionScalarValue.NegativeIntegerLiteral() != null) {
valuePath = UninterpretedOption.NEGATIVE_INT_VALUE_FIELD_NUMBER;
} else if (optionScalarValue.IntegerLiteral() != null) {
valuePath = UninterpretedOption.POSITIVE_INT_VALUE_FIELD_NUMBER;
} else { // we shouldn't arrive here!
throw new RuntimeException("custom option value has unidentified type!");
}
}
locationBuilder.addLocationForPrimitive(valuePath).setAllSpan(customOptionValue)
// exit Option scope
.popScope();
// should the aggregate locations be added here? BTW, protoc fails on
// aggregates!
}
private void doStandardOption(final ParserRuleContext ctx, final ParserRuleContext parentCtx,
final int customOptionValueType) {
locationBuilder.addOptionLocation().setAllSpan(parentCtx).comments(parentCtx);
if (treatStandardOptionAsUninterpreted) {
locationBuilder.addOptionNameLocation().setAllSpan((TerminalNode) ctx.getChild(0))
.addLocationForPrimitive(UninterpretedOption.NAME_FIELD_NUMBER)
.setAllSpan((TerminalNode) ctx.getChild(0))
.addLocationForPrimitive(UninterpretedOption.NamePart.NAME_PART_FIELD_NUMBER)
.setAllSpan((TerminalNode) ctx.getChild(0))
// exit OptionName scope
.popScope()
.addLocationForPrimitive(customOptionValueType).setAllSpan(ctx.getChild(2))
// exit Option scope
.popScope();
} else { // we have all needed info to do this directly!
throw new UnsupportedOperationException("standard option location is not implemented");
/*
* locationBuilder.addLocation().comments(parentCtx)
* .addPath(FileDescriptorProto.OPTIONS_FIELD_NUMBER)
* .addPath(FileOptions.getDescriptor().findFieldByName(
* ctx.getChild(0).getText()).getNumber()) .setAllSpan(ctx.getChild(2));
*/
}
}
// ***************************** Universal Options treatment END *********************
}