156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson/*
256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Copyright (C) 2010 Google Inc.
356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Licensed under the Apache License, Version 2.0 (the "License");
556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * you may not use this file except in compliance with the License.
656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * You may obtain a copy of the License at
756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * http://www.apache.org/licenses/LICENSE-2.0
956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
1056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Unless required by applicable law or agreed to in writing, software
1156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * distributed under the License is distributed on an "AS IS" BASIS,
1256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * See the License for the specific language governing permissions and
1456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * limitations under the License.
1556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */
1656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
1756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonpackage com.google.clearsilver.jsilver.data;
1856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
1956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
2056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
2156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.io.IOException;
2256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.io.LineNumberReader;
2356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.io.Reader;
2456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.ArrayList;
2556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.Iterator;
2656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.Stack;
2756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
2856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson/**
2956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Parser for HDF based on the following grammar by Brandon Long.
3056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
3156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * COMMAND := (INCLUDE | COMMENT | HDF_SET | HDF_DESCEND | HDF_ASCEND ) INCLUDE := #include
3256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * "FILENAME" EOL COMMENT := # .* EOL HDF_DESCEND := HDF_NAME_ATTRS { EOL HDF_ASCEND := } EOL
3356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * HDF_SET := (HDF_ASSIGN | HDF_MULTILINE_ASSIGN | HDF_COPY | HDF_LINK) HDF_ASSIGN := HDF_NAME_ATTRS
3456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * = .* EOL HDF_MULTILINE_ASSIGN := HDF_NAME_ATTRS << EOM_MARKER EOL (.* EOL)* EOM_MARKER EOL
3556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * HDF_COPY := HDF_NAME_ATTRS := HDF_NAME EOL HDF_LINK := HDF_NAME_ATTRS : HDF_NAME EOL
3656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * HDF_NAME_ATTRS := (HDF_NAME | HDF_NAME [HDF_ATTRS]) HDF_ATTRS := (HDF_ATTR | HDF_ATTR, HDF_ATTRS)
3756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * HDF_ATTR := (HDF_ATTR_KEY | HDF_ATTR_KEY = [^\s,\]]+ | HDF_ATTR_KEY = DQUOTED_STRING)
3856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * HDF_ATTR_KEY := [0-9a-zA-Z]+ DQUOTED_STRING := "([^\\"]|\\[ntr]|\\.)*" HDF_NAME := (HDF_SUB_NAME
3956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * | HDF_SUB_NAME\.HDF_NAME) HDF_SUB_NAME := [0-9a-zA-Z_]+ EOM_MARKER := \S.*\S EOL := \n
4056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */
4156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonpublic class NewHdfParser implements Parser {
4256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
4356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private final StringInternStrategy internStrategy;
4456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
4556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
4656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Special exception used to detect when we unexpectedly run out of characters on the line.
4756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
4856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static class OutOfCharsException extends Exception {}
4956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
5056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
5156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Object used to hold the name and attributes of an HDF node before we are ready to commit it to
5256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * the Data object.
5356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
5456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static class HdfNameAttrs {
5556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    String name;
5656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    ArrayList<String> attrs = null;
5756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int endOfSequence;
5856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
5956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    void reset(String newname) {
6056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // TODO: think about moving interning here instead of parser code
6156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.name = newname;
6256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (attrs != null) {
6356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        attrs.clear();
6456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
6556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      endOfSequence = 0;
6656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
6756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
6856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    void addAttribute(String key, String value) {
6956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (attrs == null) {
7056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        attrs = new ArrayList<String>(10);
7156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
7256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      attrs.ensureCapacity(attrs.size() + 2);
7356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // TODO: think about moving interning here instead of parser code
7456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      attrs.add(key);
7556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      attrs.add(value);
7656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
7756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
7856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Data toData(Data data) {
7956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      Data child = data.createChild(name);
8056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (attrs != null) {
8156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        Iterator<String> it = attrs.iterator();
8256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        while (it.hasNext()) {
8356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          String key = it.next();
8456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          String value = it.next();
8556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          child.setAttribute(key, value);
8656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        }
8756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
8856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return child;
8956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
9056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
9156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
9256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  static final String UNNAMED_INPUT = "[UNNAMED_INPUT]";
9356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
9456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
9556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * State information that we pass through the parse methods. Allows parser to be reentrant as all
9656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * the state is passed through method calls.
9756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
9856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  static class ParseState {
9956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    final Stack<Data> context = new Stack<Data>();
10056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    final Data output;
10156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    final LineNumberReader lineReader;
10256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    final ErrorHandler errorHandler;
10356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    final ResourceLoader resourceLoader;
10456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    final NewHdfParser hdfParser;
10556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    final boolean ignoreAttributes;
10656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    final HdfNameAttrs hdfNameAttrs;
10756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    final UniqueStack<String> includeStack;
10856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    final String parsedFileName;
10956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
11056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    String line;
11156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Data currentNode;
11256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
11356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    private ParseState(Data output, LineNumberReader lineReader, ErrorHandler errorHandler,
11456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        ResourceLoader resourceLoader, NewHdfParser hdfParser, String parsedFileName,
11556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        boolean ignoreAttributes, HdfNameAttrs hdfNameAttrs, UniqueStack<String> includeStack) {
11656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.lineReader = lineReader;
11756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.errorHandler = errorHandler;
11856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.output = output;
11956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      currentNode = output;
12056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.resourceLoader = resourceLoader;
12156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.hdfParser = hdfParser;
12256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.parsedFileName = parsedFileName;
12356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.ignoreAttributes = ignoreAttributes;
12456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.hdfNameAttrs = hdfNameAttrs;
12556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.includeStack = includeStack;
12656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
12756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
12856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    public static ParseState createNewParseState(Data output, Reader reader,
12956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        ErrorHandler errorHandler, ResourceLoader resourceLoader, NewHdfParser hdfParser,
13056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        String parsedFileName, boolean ignoreAttributes) {
13156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
13256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (parsedFileName == null) {
13356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        parsedFileName = UNNAMED_INPUT;
13456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
13556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      UniqueStack<String> includeStack = new UniqueStack<String>();
13656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      includeStack.push(parsedFileName);
13756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
13856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return new ParseState(output, new LineNumberReader(reader), errorHandler, resourceLoader,
13956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          hdfParser, parsedFileName, ignoreAttributes, new HdfNameAttrs(), includeStack);
14056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
14156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
14256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    public static ParseState createParseStateForIncludedFile(ParseState originalState,
14356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        String includeFileName, Reader includeFileReader) {
14456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return new ParseState(originalState.output, new LineNumberReader(includeFileReader),
14556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          originalState.errorHandler, originalState.resourceLoader, originalState.hdfParser,
14656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          originalState.parsedFileName, originalState.ignoreAttributes, new HdfNameAttrs(),
14756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          originalState.includeStack);
14856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
14956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
15056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
15156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
15256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
15356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Constructor for {@link NewHdfParser}.
15456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
15556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param internPool - {@link StringInternStrategy} instance used to optimize the HDF parsing.
15656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
15756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public NewHdfParser(StringInternStrategy internPool) {
15856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    this.internStrategy = internPool;
15956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
16056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
16156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static class NewHdfParserFactory implements ParserFactory {
16256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    private final StringInternStrategy stringInternStrategy;
16356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
16456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    public NewHdfParserFactory(StringInternStrategy stringInternStrategy) {
16556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      this.stringInternStrategy = stringInternStrategy;
16656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
16756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
16856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    @Override
16956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    public Parser newInstance() {
17056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return new NewHdfParser(stringInternStrategy);
17156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
17256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
17356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
17456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
17556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Creates a {@link ParserFactory} instance.
17656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
17756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * <p>
17856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Provided {@code stringInternStrategy} instance will be used by shared all {@link Parser}
17956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * objects created by the factory and used to optimize the HDF parsing process by reusing the
18056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * String for keys and values.
18156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
18256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param stringInternStrategy - {@link StringInternStrategy} instance used to optimize the HDF
18356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *        parsing.
18456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @return an instance of {@link ParserFactory} implementation.
18556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
18656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public static ParserFactory newFactory(StringInternStrategy stringInternStrategy) {
18756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return new NewHdfParserFactory(stringInternStrategy);
18856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
18956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
19056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public void parse(Reader reader, Data output, Parser.ErrorHandler errorHandler,
19156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      ResourceLoader resourceLoader, String dataFileName, boolean ignoreAttributes)
19256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throws IOException {
19356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
19456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    parse(ParseState.createNewParseState(output, reader, errorHandler, resourceLoader, this,
19556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        dataFileName, ignoreAttributes));
19656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
19756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
19856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void parse(ParseState state) throws IOException {
19956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    while ((state.line = state.lineReader.readLine()) != null) {
20056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      String seq = stripWhitespace(state.line);
20156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      try {
20256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        parseCommand(seq, state);
20356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      } catch (OutOfCharsException e) {
20456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        reportError(state, "End of line was prematurely reached. Parse error.");
20556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
20656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
20756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
20856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
20956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static final String INCLUDE_WS = "#include ";
21056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
21156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void parseCommand(String seq, ParseState state) throws IOException, OutOfCharsException {
21256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (seq.length() == 0) {
21356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // Empty line.
21456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return;
21556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
21656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (charAt(seq, 0) == '#') {
21756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // If there isn't a match on include then this is a comment and we do nothing.
21856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (matches(seq, 0, INCLUDE_WS)) {
21956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        // This is an include command
22056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        int start = skipLeadingWhitespace(seq, INCLUDE_WS.length());
22156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        parseInclude(seq, start, state);
22256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
22356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return;
22456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else if (charAt(seq, 0) == '}') {
22556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (skipLeadingWhitespace(seq, 1) != seq.length()) {
22656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        reportError(state, "Extra chars after '}'");
22756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return;
22856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
22956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      handleAscend(state);
23056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else {
23156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      parseHdfElement(seq, state);
23256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
23356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
23456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
23556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void parseInclude(String seq, int start, ParseState state) throws IOException,
23656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      OutOfCharsException {
23756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int end = seq.length();
23856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (charAt(seq, start) == '"') {
23956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (charAt(seq, end - 1) == '"') {
24056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        start++;
24156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        end--;
24256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      } else {
24356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        reportError(state, "Missing '\"' at end of include");
24456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return;
24556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
24656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
24756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    handleInclude(seq.substring(start, end), state);
24856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
24956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
25056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static final int NO_MATCH = -1;
25156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
25256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void parseHdfElement(String seq, ParseState state) throws IOException,
25356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      OutOfCharsException {
25456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // Re-use a single element to avoid repeated allocations/trashing (serious
25556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // performance impact, 5% of real service performance)
25656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    HdfNameAttrs element = state.hdfNameAttrs;
25756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (!parseHdfNameAttrs(element, seq, 0, state)) {
25856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return;
25956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
26056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int index = skipLeadingWhitespace(seq, element.endOfSequence);
26156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    switch (charAt(seq, index)) {
26256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      case '{':
26356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        // Descend
26456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        if (index + 1 != seq.length()) {
26556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          reportError(state, "No characters expected after '{'");
26656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          return;
26756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        }
26856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        handleDescend(state, element);
26956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return;
27056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      case '=':
27156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        // Assignment
27256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        index = skipLeadingWhitespace(seq, index + 1);
27356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        String value = internStrategy.intern(seq.substring(index, seq.length()));
27456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        handleAssign(state, element, value);
27556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return;
27656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      case ':':
27756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        if (charAt(seq, index + 1) == '=') {
27856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          // Copy
27956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          index = skipLeadingWhitespace(seq, index + 2);
28056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          String src = parseHdfName(seq, index);
28156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          if (src == null) {
28256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            reportError(state, "Invalid HDF name");
28356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            return;
28456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          }
28556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          if (index + src.length() != seq.length()) {
28656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            reportError(state, "No characters expected after '{'");
28756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            return;
28856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          }
28956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          handleCopy(state, element, src);
29056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        } else {
29156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          // Link
29256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          index = skipLeadingWhitespace(seq, index + 1);
29356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          String src = parseHdfName(seq, index);
29456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          if (src == null) {
29556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            reportError(state, "Invalid HDF name");
29656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            return;
29756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          }
29856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          if (index + src.length() != seq.length()) {
29956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            reportError(state, "No characters expected after '{'");
30056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            return;
30156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          }
30256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          handleLink(state, element, src);
30356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        }
30456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return;
30556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      case '<':
30656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        if (charAt(seq, index + 1) != '<') {
30756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          reportError(state, "Expected '<<'");
30856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        }
30956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        index = skipLeadingWhitespace(seq, index + 2);
31056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        String eomMarker = seq.substring(index, seq.length());
31156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        // TODO: think about moving interning to handleAssign()
31256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        String multilineValue = internStrategy.intern(parseMultilineValue(state, eomMarker));
31356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        if (multilineValue == null) {
31456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          return;
31556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        }
31656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        handleAssign(state, element, multilineValue);
31756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return;
31856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      default:
31956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        reportError(state, "No valid operator");
32056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return;
32156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
32256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
32356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
32456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
32556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * This method parses out an HDF element name and any optional attributes into a caller-supplied
32656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * HdfNameAttrs object. It returns a {@code boolean} with whether it succeeded to parse.
32756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
32856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private boolean parseHdfNameAttrs(HdfNameAttrs destination, String seq, int index,
32956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      ParseState state) throws OutOfCharsException {
33056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    String hdfName = parseHdfName(seq, index);
33156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (hdfName == null) {
33256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      reportError(state, "Invalid HDF name");
33356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return false;
33456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
33556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    destination.reset(hdfName);
33656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    index = skipLeadingWhitespace(seq, index + hdfName.length());
33756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int end = parseAttributes(seq, index, state, destination);
33856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (end == NO_MATCH) {
33956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // Error already reported below.
34056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return false;
34156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else {
34256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      destination.endOfSequence = end;
34356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return true;
34456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
34556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
34656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
34756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
34856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Parses a valid hdf path name.
34956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
35056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private String parseHdfName(String seq, int index) throws OutOfCharsException {
35156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int end = index;
35256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    while (end < seq.length() && isHdfNameChar(charAt(seq, end))) {
35356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      end++;
35456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
35556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (end == index) {
35656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return null;
35756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
35856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return internStrategy.intern(seq.substring(index, end));
35956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
36056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
36156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
36256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Looks for optional attributes and adds them to the HdfNameAttrs object passed into the method.
36356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
36456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private int parseAttributes(String seq, int index, ParseState state, HdfNameAttrs element)
36556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throws OutOfCharsException {
36656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (charAt(seq, index) != '[') {
36756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // No attributes to parse
36856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return index;
36956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
37056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    index = skipLeadingWhitespace(seq, index + 1);
37156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
37256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // If we don't care about attributes, just skip over them.
37356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (state.ignoreAttributes) {
37456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      while (charAt(seq, index) != ']') {
37556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        index++;
37656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
37756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return index + 1;
37856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
37956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
38056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    boolean first = true;
38156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    do {
38256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (first) {
38356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        first = false;
38456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      } else if (charAt(seq, index) == ',') {
38556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        index = skipLeadingWhitespace(seq, index + 1);
38656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      } else {
38756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        reportError(state, "Error parsing attribute list");
38856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
38956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      index = parseAttribute(seq, index, state, element);
39056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (index == NO_MATCH) {
39156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        // reportError called by parseAttribute already.
39256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return NO_MATCH;
39356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
39456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      index = skipLeadingWhitespace(seq, index);
39556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } while (charAt(seq, index) != ']');
39656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return index + 1;
39756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
39856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
39956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static final String DEFAULT_ATTR_VALUE = "1";
40056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
40156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
40256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Parse out a single HDF attribute. If there is no explicit value, use default value of "1" like
40356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * in C clearsilver. Returns NO_MATCH if it fails to parse an attribute.
40456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
40556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private int parseAttribute(String seq, int index, ParseState state, HdfNameAttrs element)
40656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throws OutOfCharsException {
40756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int end = parseAttributeKey(seq, index);
40856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (index == end) {
40956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      reportError(state, "No valid attribute key");
41056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return NO_MATCH;
41156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
41256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    String attrKey = internStrategy.intern(seq.substring(index, end));
41356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    index = skipLeadingWhitespace(seq, end);
41456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (charAt(seq, index) != '=') {
41556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // No value for this attribute key. Use default value of "1"
41656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      element.addAttribute(attrKey, DEFAULT_ATTR_VALUE);
41756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return index;
41856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
41956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // We need to parse out the attribute value.
42056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    index = skipLeadingWhitespace(seq, index + 1);
42156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (charAt(seq, index) == '"') {
42256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      index++;
42356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      StringBuilder sb = new StringBuilder();
42456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      end = parseQuotedAttributeValue(seq, index, sb);
42556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (end == NO_MATCH) {
42656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        reportError(state, "Unable to parse quoted attribute value");
42756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return NO_MATCH;
42856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
42956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      String attrValue = internStrategy.intern(sb.toString());
43056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      element.addAttribute(attrKey, attrValue);
43156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      end++;
43256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else {
43356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // Simple attribute that has no whitespace.
43456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      String attrValue = parseAttributeValue(seq, index, state);
43556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (attrValue == null || attrValue.length() == 0) {
43656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        reportError(state, "No attribute for key " + attrKey);
43756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return NO_MATCH;
43856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
43956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
44056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      attrValue = internStrategy.intern(attrValue);
44156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      element.addAttribute(attrKey, attrValue);
44256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      end = index + attrValue.length();
44356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
44456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return end;
44556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
44656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
44756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
44856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Returns the range in the sequence starting at start that corresponds to a valid attribute key.
44956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
45056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private int parseAttributeKey(String seq, int index) throws OutOfCharsException {
45156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    while (isAlphaNumericChar(charAt(seq, index))) {
45256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      index++;
45356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
45456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return index;
45556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
45656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
45756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
45856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Parses a quoted attribute value. Unescapes octal characters and \n, \r, \t, \", etc.
45956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
46056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private int parseQuotedAttributeValue(String seq, int index, StringBuilder sb)
46156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throws OutOfCharsException {
46256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    char c;
46356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    while ((c = charAt(seq, index)) != '"') {
46456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (c == '\\') {
46556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        // Escaped character. Look for 1 to 3 digits in a row as octal or n,t,r.
46656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        index++;
46756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        char next = charAt(seq, index);
46856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        if (isNumericChar(next)) {
46956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          // Parse the next 1 to 3 characters if they are digits. Treat it as an octal code.
47056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          int val = next - '0';
47156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          if (isNumericChar(charAt(seq, index + 1))) {
47256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            index++;
47356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            val = val * 8 + (charAt(seq, index) - '0');
47456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            if (isNumericChar(charAt(seq, index + 1))) {
47556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson              index++;
47656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson              val = val * 8 + (charAt(seq, index) - '0');
47756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson            }
47856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          }
47956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          c = (char) val;
48056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        } else if (next == 'n') {
48156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          c = '\n';
48256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        } else if (next == 't') {
48356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          c = '\t';
48456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        } else if (next == 'r') {
48556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          c = '\r';
48656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        } else {
48756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          // Regular escaped char like " or /
48856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          c = next;
48956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        }
49056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
49156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      sb.append(c);
49256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      index++;
49356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
49456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return index;
49556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
49656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
49756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
49856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Parses a simple attribute value that cannot have any whitespace or specific punctuation
49956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * reserved by the HDF grammar.
50056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
50156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private String parseAttributeValue(String seq, int index, ParseState state)
50256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throws OutOfCharsException {
50356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int end = index;
50456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    char c = charAt(seq, end);
50556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    while (c != ',' && c != ']' && c != '"' && !Character.isWhitespace(c)) {
50656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      end++;
50756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      c = charAt(seq, end);
50856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
50956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return seq.substring(index, end);
51056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
51156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
51256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private String parseMultilineValue(ParseState state, String eomMarker) throws IOException {
51356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    StringBuilder sb = new StringBuilder(256);
51456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    String line;
51556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    while ((line = state.lineReader.readLine()) != null) {
51656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (line.startsWith(eomMarker)
51756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          && skipLeadingWhitespace(line, eomMarker.length()) == line.length()) {
51856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return sb.toString();
51956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      } else {
52056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        sb.append(line).append('\n');
52156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
52256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
52356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    reportError(state, "EOM " + eomMarker + " never found");
52456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return null;
52556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
52656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
52756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  // //////////////////////////////////////////////////////////////////////////
52856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  //
52956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  // Handlers
53056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
53156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void handleDescend(ParseState state, HdfNameAttrs element) {
53256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Data child = handleNodeCreation(state.currentNode, element);
53356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    state.context.push(state.currentNode);
53456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    state.currentNode = child;
53556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
53656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
53756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private Data handleNodeCreation(Data node, HdfNameAttrs element) {
53856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return element.toData(node);
53956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
54056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
54156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void handleAssign(ParseState state, HdfNameAttrs element, String value) {
54256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // TODO: think about moving interning here
54356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Data child = handleNodeCreation(state.currentNode, element);
54456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    child.setValue(value);
54556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
54656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
54756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void handleCopy(ParseState state, HdfNameAttrs element, String srcName) {
54856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Data child = handleNodeCreation(state.currentNode, element);
54956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Data src = state.output.getChild(srcName);
55056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (src != null) {
55156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      child.setValue(src.getValue());
55256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else {
55356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      child.setValue("");
55456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
55556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
55656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
55756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void handleLink(ParseState state, HdfNameAttrs element, String srcName) {
55856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Data child = handleNodeCreation(state.currentNode, element);
55956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    child.setSymlink(state.output.createChild(srcName));
56056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
56156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
56256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void handleAscend(ParseState state) {
56356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (state.context.isEmpty()) {
56456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      reportError(state, "Too many '}'");
56556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return;
56656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
56756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    state.currentNode = state.context.pop();
56856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
56956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
57056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void handleInclude(String seq, ParseState state) throws IOException {
57156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    String includeFileName = internStrategy.intern(seq);
57256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
57356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // Load the file
57456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Reader reader = state.resourceLoader.open(includeFileName);
57556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (reader == null) {
57656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      reportError(state, "Unable to find file " + includeFileName);
57756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return;
57856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
57956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
58056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // Check whether we are in include loop
58156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (!state.includeStack.push(includeFileName)) {
58256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      reportError(state, createIncludeStackTraceMessage(state.includeStack, includeFileName));
58356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return;
58456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
58556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
58656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // Parse the file
58756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    state.hdfParser.parse(ParseState
58856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        .createParseStateForIncludedFile(state, includeFileName, reader));
58956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
59056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (!includeFileName.equals(state.includeStack.pop())) {
59156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // Include stack trace is corrupted
59256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throw new IllegalStateException("Unable to find on include stack: " + includeFileName);
59356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
59456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
59556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
59656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private String createIncludeStackTraceMessage(UniqueStack<String> includeStack,
59756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      String includeFileName) {
59856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    StringBuilder message = new StringBuilder();
59956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    message.append("File included twice: ");
60056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    message.append(includeFileName);
60156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
60256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    message.append(" Include stack: ");
60356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    for (String fileName : includeStack) {
60456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      message.append(fileName);
60556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      message.append(" -> ");
60656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
60756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    message.append(includeFileName);
60856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return message.toString();
60956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
61056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
61156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  // /////////////////////////////////////////////////////////////////////////
61256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  //
61356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  // Character values
61456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
61556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static boolean isNumericChar(char c) {
61656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if ('0' <= c && c <= '9') {
61756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return true;
61856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else {
61956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return false;
62056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
62156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
62256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
62356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static boolean isAlphaNumericChar(char c) {
62456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) {
62556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return true;
62656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else {
62756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return false;
62856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
62956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
63056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
63156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static boolean isHdfNameChar(char c) {
63256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (isAlphaNumericChar(c) || c == '_' || c == '.') {
63356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return true;
63456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else {
63556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return false;
63656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
63756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
63856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
63956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static String stripWhitespace(String seq) {
64056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int start = skipLeadingWhitespace(seq, 0);
64156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int end = seq.length() - 1;
64256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    while (end > start && Character.isWhitespace(seq.charAt(end))) {
64356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      --end;
64456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
64556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (start == 0 && end == seq.length() - 1) {
64656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return seq;
64756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else {
64856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return seq.substring(start, end + 1);
64956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
65056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
65156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
65256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static int skipLeadingWhitespace(String seq, int index) {
65356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    while (index < seq.length() && Character.isWhitespace(seq.charAt(index))) {
65456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      index++;
65556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
65656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return index;
65756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
65856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
65956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
66056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Determines if a character sequence appears in the given sequence starting at a specified index.
66156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
66256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param seq the sequence that we want to see if it contains the string match.
66356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param start the index into seq where we want to check for match
66456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param match the String we want to look for in the sequence.
66556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @return {@code true} if the string match appears in seq starting at the index start, {@code
66656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *         false} otherwise.
66756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
66856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static boolean matches(String seq, int start, String match) {
66956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (seq.length() - start < match.length()) {
67056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return false;
67156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
67256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    for (int i = 0; i < match.length(); i++) {
67356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (match.charAt(i) != seq.charAt(start + i)) {
67456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        return false;
67556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
67656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
67756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return true;
67856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
67956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
68056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
68156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Reads the character at the specified index in the given String. Throws an exception to be
68256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * caught above if the index is out of range.
68356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
68456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static char charAt(String seq, int index) throws OutOfCharsException {
68556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (0 <= index && index < seq.length()) {
68656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return seq.charAt(index);
68756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else {
68856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throw new OutOfCharsException();
68956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
69056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
69156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
69256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
69356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static void reportError(ParseState state, String errorMessage) {
69456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (state.errorHandler != null) {
69556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      state.errorHandler.error(state.lineReader.getLineNumber(), state.line, state.parsedFileName,
69656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          errorMessage);
69756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } else {
69856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throw new RuntimeException("Parse Error on line " + state.lineReader.getLineNumber() + ": "
69956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          + errorMessage + " : " + state.line);
70056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
70156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
70256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson}
703