OptionElement option = readOption(keyValueSeparator); String name = option.name(); Object value = option.value(); } else if (previous instanceof List) { addToList((List<Object>) previous, value); } else { List<Object> newList = new ArrayList<>(); newList.add(previous); addToList(newList, value); result.put(name, newList);
/** * Returns a list of values. This is similar to JSON with '[' and ']' surrounding the list and ',' * separating values. */ private List<Object> readList() { reader.require('['); List<Object> result = new ArrayList<>(); while (true) { // If we see the close brace, finish immediately. This handles [] and ,] cases. if (reader.peekChar(']')) return result; result.add(readKindAndValue().value()); if (reader.peekChar(',')) continue; if (reader.peekChar() != ']') throw reader.unexpected("expected ',' or ']'"); } }
withOptions.add(new OptionReader(reader).readOption('=')); reader.require(';'); break;
/** Reads an enum constant like "ROCK = 0;". The label is the constant name. */ private EnumConstantElement readEnumConstant( String documentation, Location location, String label) { reader.require('='); int tag = reader.readInt(); ImmutableList<OptionElement> options = new OptionReader(reader).readOptions(); reader.require(';'); documentation = reader.tryAppendTrailingDocumentation(documentation); return EnumConstantElement.builder(location) .name(label) .tag(tag) .documentation(documentation) .options(options) .build(); }
/** * Reads options enclosed in '[' and ']' if they are present and returns them. Returns an empty * list if no options are present. */ public ImmutableList<OptionElement> readOptions() { if (!reader.peekChar('[')) return ImmutableList.of(); ImmutableList.Builder<OptionElement> result = ImmutableList.builder(); while (true) { result.add(readOption('=')); // Check for closing ']' if (reader.peekChar(']')) break; // Discard optional ','. if (!reader.peekChar(',')) throw reader.unexpected("Expected ',' or ']"); } return result.build(); }
/** Reads a value that can be a map, list, string, number, boolean or enum. */ private KindAndValue readKindAndValue() { char peeked = reader.peekChar(); switch (peeked) { case '{': return KindAndValue.of(MAP, readMap('{', '}', ':')); case '[': return KindAndValue.of(LIST, readList()); case '"': case '\'': return KindAndValue.of(STRING, reader.readString()); default: if (Character.isDigit(peeked) || peeked == '-') { return KindAndValue.of(NUMBER, reader.readWord()); } String word = reader.readWord(); switch (word) { case "true": return KindAndValue.of(BOOLEAN, "true"); case "false": return KindAndValue.of(BOOLEAN, "false"); default: return KindAndValue.of(ENUM, word); } } }
return null; } else if (label.equals("option")) { OptionElement result = new OptionReader(reader).readOption('='); reader.require(';'); return result;
/** Reads an field declaration and returns it. */ private FieldElement readField( Location location, String documentation, @Nullable Field.Label label, String type) { String name = reader.readName(); reader.require('='); int tag = reader.readInt(); FieldElement.Builder builder = FieldElement.builder(location) .label(label) .type(type) .name(name) .tag(tag); List<OptionElement> options = new OptionReader(reader).readOptions(); reader.require(';'); options = new ArrayList<>(options); // Mutable copy for extractDefault. String defaultValue = stripDefault(options); documentation = reader.tryAppendTrailingDocumentation(documentation); return builder.documentation(documentation) .defaultValue(defaultValue) .options(ImmutableList.copyOf(options)) .build(); }
/** Reads a option containing a name, an '=' or ':', and a value. */ public OptionElement readOption(char keyValueSeparator) { boolean isExtension = (reader.peekChar() == '['); boolean isParenthesized = (reader.peekChar() == '('); String name = reader.readName(); // Option name. if (isExtension) { name = "[" + name + "]"; } String subName = null; char c = reader.readChar(); if (c == '.') { // Read nested field name. For example "baz" in "(foo.bar).baz = 12". subName = reader.readName(); c = reader.readChar(); } if (keyValueSeparator == ':' && c == '{') { // In text format, values which are maps can omit a separator. Backtrack so it can be re-read. reader.pushBack('{'); } else if (c != keyValueSeparator) { throw reader.unexpected("expected '" + keyValueSeparator + "' in option"); } KindAndValue kindAndValue = readKindAndValue(); Kind kind = kindAndValue.kind(); Object value = kindAndValue.value(); if (subName != null) { value = OptionElement.create(subName, kind, value); kind = Kind.OPTION; } return OptionElement.create(name, kind, value, isParenthesized); }