Subversion Repositories general

Rev

Rev 1005 | Blame | Compare with Previous | Last modification | View Log | RSS feed

package ak.zpath;

import java.util.List;
import java.util.ArrayList;

public class PathParser
{
  public List parse(String path)
    throws PathParseException
  {
    char   c;
    int    startPos;
    int    pos      = 0;
    int    endPos   = path.length();
    String token;
    List   tokens = new ArrayList();

    while(pos < endPos) {
      c = path.charAt(pos);

      while(Character.isWhitespace(c) && pos < endPos - 1) {
        c = path.charAt(++pos);
      }
      if(pos >= endPos) break;

      switch(c) {
        case '/':
          addToken(tokens, pos, Token.TOKEN_SLASH, null);
          pos++;
          break;

        case '[':
          addToken(tokens, pos, Token.TOKEN_OPEN_BRACKET, null);
          pos++;
          break;

        case ']':
          addToken(tokens, pos, Token.TOKEN_CLOSE_BRACKET, null);
          pos++;
          break;

        case '@':
          addToken(tokens, pos, Token.TOKEN_AT, null);
          pos++;
          break;

        case '#':
          addToken(tokens, pos, Token.TOKEN_SHARP, null);
          pos++;
          break;

        case '"':
        case '\'':
          startPos = pos+1;
          pos = findStringEnd(path, c, startPos);
          addToken(tokens, startPos-1, Token.TOKEN_STRING,
            path.substring(startPos, pos));
          pos++;
          break;

        case '=':
          addToken(tokens, pos, Token.TOKEN_EQUAL, null);
          pos++;
          break;

        case '$':
          if(++pos >= endPos)
            throw new PathParseException("variable name expected", pos);
          c = path.charAt(pos);
          if(c == '{') {
            startPos = ++pos;
            pos = findVariableEnd(path, c, startPos);
            addToken(tokens, startPos-2,
              Token.TOKEN_VARIABLE, path.substring(startPos, pos));
          }
          else if(Character.isLetter(c)) {
            startPos = pos;
            pos = findVariableEnd(path, '\u0000', pos);
            addToken(tokens, startPos-1,
              Token.TOKEN_VARIABLE, path.substring(startPos, pos));
          }
          else
            throw new PathParseException("variable name expected", pos);
          pos++;
          break;

        case ':':
          if(++pos >= endPos)
            throw new PathParseException("'=' expected", pos);
          c = path.charAt(pos);
          if(c == '=')
            addToken(tokens, pos-1, Token.TOKEN_ASSIGN, null);
          else
            throw new PathParseException("'=' expected", pos);
          pos++;
          break;

        case '.':
          if(++pos >= endPos)
            throw new PathParseException("'=' or '.' expected", pos);
          c = path.charAt(pos);
          if(c == '=')
            addToken(tokens, pos-1, Token.TOKEN_APPEND, null);
          else if(c == '.')
            addToken(tokens, pos-1, Token.TOKEN_NAME, "..");
          else
            throw new PathParseException("'=' expected", pos);
          pos++;
          break;

        case '*':
          if(pos < endPos-1 && path.charAt(pos+1) == '*') {
            addToken(tokens, pos, Token.TOKEN_NAME, "**");
            pos += 2;
          }
          else {
            addToken(tokens, pos, Token.TOKEN_NAME, "*");
            pos++;
          }
          break;

        default:
          if(Character.isDigit(c)) {
            startPos = pos;
            pos = findIntegerEnd(path, pos);
            token = path.substring(startPos, pos);
            addToken(tokens, startPos, Token.TOKEN_INDEX, token);
          }
          else if(Character.isLetter(c) || c == '_') {
            startPos = pos;
            pos = findTokenEnd(path, pos);
            token = path.substring(startPos, pos);
            if("and".equals(token))
              addToken(tokens, startPos, Token.TOKEN_AND, null);
            else if("or".equals(token))
              addToken(tokens, startPos, Token.TOKEN_OR, null);
            else if("new".equals(token))
              addToken(tokens, startPos, Token.TOKEN_NEW, null);
            else
              addToken(tokens, startPos, Token.TOKEN_NAME, token);
          }
          else
            throw new PathParseException(
              "unexpected character '" + c + "'", pos);
      }
    }

    return tokens;
  }

  private int findStringEnd(String path, char c, int pos)
    throws PathParseException
  {
    int endPos = path.length();

    if(pos >= endPos - 1)
      throw new PathParseException(
        "unclosed string, '" + c + "' expected", pos);

    while(path.charAt(++pos) != c) {
      if(pos >= endPos - 1)
        throw new PathParseException(
          "unclosed string, '" + c + "' expected", pos);
    }

    return pos;
  }

  private int findVariableEnd(String path, char c, int pos)
    throws PathParseException
  {
    int endPos = path.length();

    if(pos >= endPos)
      throw new PathParseException("unexpected end of variable name", pos);

    char c2 = path.charAt(pos);

    if(c == '{' && c2 == '}')
      throw new PathParseException("empty variable name", pos);
    else if(!Character.isLetter(c2) && c2 != '_')
      throw new PathParseException("variable name expected", pos);

    while(true) {
      if(pos >= endPos - 1)
        throw new PathParseException("unexpected end of variable name", pos);

      c2 = path.charAt(++pos);

      if(c == '{') {
        if(c2 == '}') break;
        if(!Character.isLetter(c2) && !Character.isDigit(c2) && c2 != '_')
          throw new PathParseException("unexpected character '" + c2
            + "' in variable name is occured instead of '}'", pos);
      }
      else {
        if(!Character.isLetter(c2) && !Character.isDigit(c2) && c2 != '_')
          break;
        if(pos >= endPos - 1) {
          pos++;
          break;
        }
      }
    }

    return pos;
  }

  private int findTokenEnd(String path, int pos)
    throws PathParseException
  {
    int endPos = path.length();
    char c;

    while(true) {
      pos++;
      if(pos >= endPos) break;

      c = path.charAt(pos);
      if(!Character.isLetter(c) && !Character.isDigit(c) && c != '_') break;
    }

    return pos;
  }

  private int findIntegerEnd(String path, int pos)
    throws PathParseException
  {
    int endPos = path.length();
    char c;

    while(true) {
      pos++;
      if(pos >= endPos) break;

      c = path.charAt(pos);
      if(!Character.isDigit(c)) break;
    }

    return pos;
  }

  private void addToken(List tokens, int pos, int type, String value)
  {
    tokens.add(new Token(type, pos, value));
  }
}