/** * Peeks a non-whitespace character and returns it. The only difference * between this and {@code readChar} is that this doesn't consume the char. */ private char peekChar() { skipWhitespace(true); if (pos == data.length) throw unexpected("unexpected end of file"); return data[pos]; }
private char readNumericEscape(int radix, int len) { int value = -1; for (int endPos = Math.min(pos + len, data.length); pos < endPos; pos++) { int digit = hexDigit(data[pos]); if (digit == -1 || digit >= radix) break; if (value < 0) { value = digit; } else { value = value * radix + digit; } } if (value < 0) throw unexpected("expected a digit after \\x or \\X"); return (char) value; }
/** Reads an integer and returns it. */ private int readInt() { String tag = readWord(); try { int radix = 10; if (tag.startsWith("0x") || tag.startsWith("0X")) { tag = tag.substring("0x".length()); radix = 16; } return Integer.valueOf(tag, radix); } catch (Exception e) { throw unexpected("expected an integer but was " + tag); } }
private RuntimeException unexpected(String message) { return unexpected(location(), message); }
/** Reads a non-empty word and returns it. */ private String readWord() { skipWhitespace(true); int start = pos; while (pos < data.length) { char c = data[pos]; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_') || (c == '-') || (c == '.')) { pos++; } else { break; } } if (start == pos) throw unexpected("expected a word"); return new String(data, start, pos - start); }
/** Reads a scalar, map, or type name with {@code name} as a prefix word. */ private String readDataType(String name) { if (name.equals("map")) { if (readChar() != '<') throw unexpected("expected '<'"); String keyType = readDataType(); if (readChar() != ',') throw unexpected("expected ','"); String valueType = readDataType(); if (readChar() != '>') throw unexpected("expected '>'"); return String.format("map<%s, %s>", keyType, valueType); } else { return name; } }
throw unexpected("unterminated comment"); } else if (commentType == '/') { if (pos < data.length && data[pos] == ' ') { throw unexpected("unexpected '/'");
/** Reads extensions like "extensions 101;" or "extensions 101 to max;". */ private ExtensionsElement readExtensions(Location location, String documentation) { int start = readInt(); // Range start. int end = start; if (peekChar() != ';') { if (!"to".equals(readWord())) throw unexpected("expected ';' or 'to'"); String s = readWord(); // Range end. if (s.equals("max")) { end = Util.MAX_TAG_VALUE; } else { end = Integer.parseInt(s); } } if (readChar() != ';') throw unexpected("expected ';'"); return ExtensionsElement.create(location, start, end, documentation); }
/** Reads a reserved tags and names list like "reserved 10, 12 to 14, 'foo';". */ private ReservedElement readReserved(Location location, String documentation) { ImmutableList.Builder<Object> valuesBuilder = ImmutableList.builder(); while (true) { char c = peekChar(); if (c == '"' || c == '\'') { valuesBuilder.add(readQuotedString()); } else { int tagStart = readInt(); c = peekChar(); if (c != ',' && c != ';') { if (!readWord().equals("to")) { throw unexpected("expected ',', ';', or 'to'"); } int tagEnd = readInt(); valuesBuilder.add(Range.closed(tagStart, tagEnd)); } else { valuesBuilder.add(tagStart); } } c = readChar(); if (c == ';') break; if (c != ',') throw unexpected("expected ',' or ';'"); } ImmutableList<Object> values = valuesBuilder.build(); if (values.isEmpty()) { throw unexpected("'reserved' must have at least one field name or tag"); } return ReservedElement.create(location, documentation, values); }
case "required": if (syntax == ProtoFile.Syntax.PROTO_3) { throw unexpected(location, "'required' label forbidden in proto3 field declarations"); throw unexpected(location, "'optional' label forbidden in proto3 field declarations"); throw unexpected(location, "unexpected label: " + word); throw unexpected(location, "'map' type cannot have label");
/** Reads a (paren-wrapped), [square-wrapped] or naked symbol name. */ private String readName() { String optionName; char c = peekChar(); if (c == '(') { pos++; optionName = readWord(); if (readChar() != ')') throw unexpected("expected ')'"); } else if (c == '[') { pos++; optionName = readWord(); if (readChar() != ']') throw unexpected("expected ']'"); } else { optionName = readWord(); } return optionName; }
/** * Returns a list of values. This is similar to JSON with '[' and ']' * surrounding the list and ',' separating values. */ private List<Object> readList() { if (readChar() != '[') throw new AssertionError(); List<Object> result = new ArrayList<>(); while (true) { if (peekChar() == ']') { // If we see the close brace, finish immediately. This handles [] and ,] cases. pos++; return result; } result.add(readKindAndValue().value()); char c = peekChar(); if (c == ',') { pos++; } else if (c != ']') { throw unexpected("expected ',' or ']'"); } } }
/** Reads an enumerated type declaration and returns it. */ private EnumElement readEnumElement(Location location, String documentation) { String name = readName(); EnumElement.Builder builder = EnumElement.builder(location) .name(name) .documentation(documentation); if (readChar() != '{') throw unexpected("expected '{'"); ImmutableList.Builder<EnumConstantElement> constants = ImmutableList.builder(); ImmutableList.Builder<OptionElement> options = ImmutableList.builder(); while (true) { String valueDocumentation = readDocumentation(); if (peekChar() == '}') { pos++; break; } Object declared = readDeclaration(valueDocumentation, Context.ENUM); if (declared instanceof EnumConstantElement) { constants.add((EnumConstantElement) declared); } else if (declared instanceof OptionElement) { options.add((OptionElement) declared); } } return builder.options(options.build()) .constants(constants.build()) .build(); }
private OneOfElement readOneOf(String documentation) { OneOfElement.Builder builder = OneOfElement.builder() .name(readName()) .documentation(documentation); ImmutableList.Builder<FieldElement> fields = ImmutableList.builder(); ImmutableList.Builder<GroupElement> groups = ImmutableList.builder(); if (readChar() != '{') throw unexpected("expected '{'"); while (true) { String nestedDocumentation = readDocumentation(); if (peekChar() == '}') { pos++; break; } Location location = location(); String type = readDataType(); if (type.equals("group")) { groups.add(readGroup(nestedDocumentation, null)); } else { fields.add(readField(location, nestedDocumentation, null, type)); } } return builder.fields(fields.build()) .groups(groups.build()) .build(); }
/** Reads a service declaration and returns it. */ private ServiceElement readService(Location location, String documentation) { String name = readName(); ServiceElement.Builder builder = ServiceElement.builder(location) .name(name) .documentation(documentation); if (readChar() != '{') throw unexpected("expected '{'"); ImmutableList.Builder<RpcElement> rpcs = ImmutableList.builder(); ImmutableList.Builder<OptionElement> options = ImmutableList.builder(); while (true) { String rpcDocumentation = readDocumentation(); if (peekChar() == '}') { pos++; break; } Object declared = readDeclaration(rpcDocumentation, Context.SERVICE); if (declared instanceof RpcElement) { rpcs.add((RpcElement) declared); } else if (declared instanceof OptionElement) { options.add((OptionElement) declared); } } return builder.options(options.build()) .rpcs(rpcs.build()) .build(); }
.documentation(documentation); if (readChar() != '(') throw unexpected("expected '('"); String type; String word = readWord(); if (readChar() != ')') throw unexpected("expected ')'"); if (!readWord().equals("returns")) throw unexpected("expected 'returns'"); if (readChar() != '(') throw unexpected("expected '('"); word = readWord(); if (word.equals("stream")) { if (readChar() != ')') throw unexpected("expected ')'"); } else if (readChar() != ';') throw unexpected("expected ';'");
/** Reads an extend declaration. */ private ExtendElement readExtend(Location location, String documentation) { String name = readName(); ExtendElement.Builder builder = ExtendElement.builder(location) .name(name) .documentation(documentation); if (readChar() != '{') throw unexpected("expected '{'"); ImmutableList.Builder<FieldElement> fields = ImmutableList.builder(); while (true) { String nestedDocumentation = readDocumentation(); if (peekChar() == '}') { pos++; break; } Object declared = readDeclaration(nestedDocumentation, Context.EXTEND); if (declared instanceof FieldElement) { fields.add((FieldElement) declared); } } return builder.fields(fields.build()) .build(); }
private GroupElement readGroup(String documentation, Field.Label label) { String name = readWord(); if (readChar() != '=') { throw unexpected("expected '='"); } int tag = readInt(); GroupElement.Builder builder = GroupElement.builder() .label(label) .name(name) .tag(tag) .documentation(documentation); ImmutableList.Builder<FieldElement> fields = ImmutableList.builder(); if (readChar() != '{') throw unexpected("expected '{'"); while (true) { String nestedDocumentation = readDocumentation(); if (peekChar() == '}') { pos++; break; } Location location = location(); String fieldLabel = readWord(); Object field = readField(nestedDocumentation, location, fieldLabel); if (!(field instanceof FieldElement)) { throw unexpected("expected field declaration, was " + field); } fields.add((FieldElement) field); } return builder.fields(fields.build()) .build(); }
Location location, String documentation, @Nullable Field.Label label, String type) { String name = readName(); if (readChar() != '=') throw unexpected("expected '='"); int tag = readInt(); throw unexpected("expected ';'");
/** Reads a option containing a name, an '=' or ':', and a value. */ private OptionElement readOption(char keyValueSeparator) { boolean isExtension = (peekChar() == '['); boolean isParenthesized = (peekChar() == '('); String name = readName(); // Option name. if (isExtension) { name = "[" + name + "]"; } String subName = null; char c = readChar(); if (c == '.') { // Read nested field name. For example "baz" in "(foo.bar).baz = 12". subName = readName(); c = readChar(); } if (keyValueSeparator == ':' && c == '{') { // In text format, values which are maps can omit a separator. Backtrack so it can be re-read. pos--; } else if (c != keyValueSeparator) { throw unexpected("expected '" + keyValueSeparator + "' in option"); } OptionKindAndValue kindAndValue = readKindAndValue(); OptionElement.Kind kind = kindAndValue.kind(); Object value = kindAndValue.value(); if (subName != null) { value = OptionElement.create(subName, kind, value); kind = OptionElement.Kind.OPTION; } return OptionElement.create(name, kind, value, isParenthesized); }