Package com.google.devtools.moe.client.parser

Source Code of com.google.devtools.moe.client.parser.Parser

// Copyright 2011 The MOE Authors All Rights Reserved.

package com.google.devtools.moe.client.parser;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import java.io.IOException;
import java.io.StringReader;
import java.io.StreamTokenizer;
import java.lang.IllegalArgumentException;
import java.util.List;
import java.util.Map;

/**
* Parser for Terms; useful for various Expression Languages
*
* This allows MOE to embed options in one string instead of having many
* orthogonal flags.
*
* The syntax for a Term is:
*
* TERM -> LITERAL OPTIONS
* OPTIONS ->
* OPTIONS -> (OPTIONLIST)
* OPTIONLIST -> OPTION
* OPTIONLIST ->
* OPTIONLIST -> OPTIONLIST , OPTION
* OPTION -> LITERAL = LITERAL
* LITERAL -> alphanumeric+
* LITERAL -> "[^"]"*
*
* Examples:
*
* internal
* internal(revision=45)
* public
* file(path="/path/to/public.tar")
*
* Users should use the high-level functions:
* parseOperator
* parseTerm
* parseTermCompletely
* tokenize
*
*
* @author dbentley@google.com (Daniel Bentley)
*/
public class Parser {
 
  private Parser() {}  // Do not instantiate.

  /** Exception for any parsing error. */
  public static class ParseError extends Exception {
    public ParseError(String error) {
      super("Cannot parse: " + error);
    }
  }

  static class ParseOptionResult {
    public String key;
    public String value;

    public ParseOptionResult(String key, String value) {
      this.key = key;
      this.value = value;
    }
  }

  public static Expression parseExpression(String toParse) throws ParseError {
    StreamTokenizer t = Parser.tokenize(toParse);
    Term creator = Parser.parseTerm(t);
    List<Operation> operations = Parser.parseOperationList(t);
    Expression ex = new RepositoryExpression(creator);
    for (Operation op : operations) {
      switch (op.operator) {
        case EDIT:
          ex = ex.editWith(op);
          break;
        case TRANSLATE:
          ex = ex.translateTo(op);
          break;
        default:
          throw new ParseError("Unexpected operator: " + op.operator);
      }
    }
    return ex;
  }

  /**
   * Parse a String under the expectation that it is a RepositoryExpression, i.e. Repository name
   * plus options, e.g. "internal(revision=1234)" or "file(path=/tmp, projectSpace=internal)".
   */
  public static RepositoryExpression parseRepositoryExpression(String toParse) throws ParseError {
    StreamTokenizer t = Parser.tokenize(toParse);
    Term creator = Parser.parseTerm(t);
    List<Operation> operations = Parser.parseOperationList(t);
    RepositoryExpression ex = new RepositoryExpression(creator);
    if (!operations.isEmpty()) {
      throw new ParseError(
          "Expression must represent a simple repository, e.g. 'internal(revision=3)'.");
    }
    return ex;
 
 
  @VisibleForTesting
  static ParseOptionResult parseOption(StreamTokenizer input) throws ParseError {
    try {

      int result = input.nextToken();
      if (result != StreamTokenizer.TT_WORD && result != '"') {
        throw new ParseError("expected word during option key parse: " + input);
      }
      String key = input.sval;

      result = input.nextToken();
      if (result != '=') {
        throw new ParseError("key and value in option must be separated by \"=\":" + input);
      }

      result = input.nextToken();
      if (result != StreamTokenizer.TT_WORD && result != '"') {
        throw new ParseError("expected word during option value parse" + input);
      }
      String value = input.sval;

      return new ParseOptionResult(key, value);
    } catch (IOException e) {
      throw new ParseError(e.getMessage());
    }
  }

  @VisibleForTesting
  static Map<String, String> parseOptions(StreamTokenizer input) throws ParseError {
    try {
      int result = input.nextToken();
      if (result == StreamTokenizer.TT_EOF) {
        return ImmutableMap.<String, String>of();
      }
      if (result != '(') {
        input.pushBack();
        return ImmutableMap.<String, String>of();
      }

      ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder();
      while (true) {
        result = input.nextToken();
        if (result == StreamTokenizer.TT_EOF) {
          throw new ParseError("options not terminated by \")\"");
        }

        if (result == ')') {
          return builder.build();
        }

        input.pushBack();
        ParseOptionResult r = parseOption(input);
        builder.put(r.key, r.value);

        result = input.nextToken();
        if (result == ')') {
          return builder.build();
        }

        if (result != ',') {
          throw new ParseError("text after option must be \",\" or \")\"");
        }
      }
    } catch (IOException e) {
      throw new ParseError(e.getMessage());
    }
  }

  /**
   * Parses a Term.
   *
   * @param input  text to parse
   *
   * @return the parsed term
   */
  public static Term parseTerm(StreamTokenizer input) throws ParseError {
    try {
      int result = input.nextToken();
      if (result != StreamTokenizer.TT_WORD && result != '"') {
        throw new ParseError("expected word during identifier parse" + input);
      }
      String identifier = input.sval;

      Map<String, String> options = parseOptions(input);
      return new Term(identifier, options);
    } catch (IOException e) {
      throw new ParseError(e.getMessage());
    }
  }

  /**
   * Determines if the input is exhausted.
   *
   * @param input  the input tokenizer
   */
  private static boolean isInputExhausted(StreamTokenizer input) throws ParseError {
    try {
      if (input.nextToken() == StreamTokenizer.TT_EOF) {
        return true;
      }
      input.pushBack();
      return false;
    } catch (IOException e) {
      throw new ParseError(e.getMessage());
    }
  }

  /**
   * Parses a Term from input, throwing an error if the input contains more than a Term.
   *
   * @param input  text to parse
   *
   * @return the parsed term
   * @throws ParseError if the string is not exactly a Term
   */
  public static Term parseTermCompletely(String input) throws ParseError {
    StreamTokenizer t = tokenize(input);
    Term result = parseTerm(t);
    if (!isInputExhausted(t)) {
      throw new ParseError("unexpected text after expression: " + t);
    }
    return result;
  }

  @VisibleForTesting
  static Operator parseOperator(StreamTokenizer input) throws ParseError {
    try {
      int operator = input.nextToken();
      try {
        Operator result = Operator.getOperator((char) operator);
        return result;
      } catch (IllegalArgumentException e) {
        throw new ParseError(
            String.format("Invalid operator \"%s\"", input.toString()));
      }
    } catch (IOException e) {
      throw new ParseError(e.getMessage());
    }
  }

  public static List<Operation> parseOperationList(StreamTokenizer input) throws ParseError {
    ImmutableList.Builder<Operation> operations = new ImmutableList.Builder<Operation>();
    while(!Parser.isInputExhausted(input)) {
      Operator operator = parseOperator(input);
      Term t = Parser.parseTerm(input);
      operations.add(new Operation(operator, t));
   }
    return operations.build();
  }

  public static StreamTokenizer tokenize(String input) {
    StreamTokenizer result =  new StreamTokenizer(new StringReader(input));
    result.resetSyntax();
    result.wordChars('a', 'z');
    result.wordChars('A', 'Z');
    result.wordChars('0', '9');
    result.whitespaceChars(0, 32);
    result.quoteChar('"');

    return result;
  }
}
TOP

Related Classes of com.google.devtools.moe.client.parser.Parser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.