All Downloads are FREE. Search and download functionalities are using the official Maven repository.

au.com.integradev.delphi.msbuild.condition.ConditionLexer Maven / Gradle / Ivy

The newest version!
/*
 * Sonar Delphi Plugin
 * Copyright (C) 2019 Integrated Application Development
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 */
package au.com.integradev.delphi.msbuild.condition;

import au.com.integradev.delphi.msbuild.condition.Token.TokenType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

public class ConditionLexer {
  public static class ConditionLexerError extends RuntimeException {
    ConditionLexerError(String message) {
      super(message);
    }
  }

  private static final Map SYNTAX_CHARACTERS =
      Map.of(
          ',', TokenType.COMMA,
          '(', TokenType.LPAREN,
          ')', TokenType.RPAREN);

  private static final Map OPERATOR_CHARACTERS =
      Map.of(
          "==", TokenType.EQUAL,
          "!=", TokenType.NOT_EQUAL,
          "!", TokenType.NOT,
          "<", TokenType.LESS_THAN,
          ">", TokenType.GREATER_THAN,
          "<=", TokenType.LESS_THAN_EQUAL,
          ">=", TokenType.GREATER_THAN_EQUAL);

  private static final Set OPERATOR_START =
      OPERATOR_CHARACTERS.keySet().stream()
          .map(key -> key.charAt(0))
          .collect(Collectors.toUnmodifiableSet());

  private static final char END_OF_INPUT = '\0';
  private String data;
  private int position;

  public List lex(String data) {
    this.data = data;
    this.position = 0;

    List result = new ArrayList<>();
    Token token;

    while ((token = readToken()) != null) {
      result.add(token);
    }

    return result;
  }

  private char peekChar() {
    if (position < data.length()) {
      return data.charAt(position);
    }
    return END_OF_INPUT;
  }

  private char getChar() {
    char character = peekChar();
    if (character != END_OF_INPUT) {
      ++position;
    }
    return character;
  }

  private void skipWhitespace() {
    char character;
    while ((character = peekChar()) != END_OF_INPUT) {
      if (!Character.isWhitespace(character)) {
        break;
      }
      ++position;
    }
  }

  @Nullable
  private Token readToken() {
    skipWhitespace();
    char character = peekChar();

    if (character != END_OF_INPUT) {
      if (OPERATOR_START.contains(character)) {
        return readOperator();
      } else if (SYNTAX_CHARACTERS.containsKey(character)) {
        return readSyntaxToken();
      } else if (character == '$') {
        return readProperty();
      } else if (character == '\'') {
        return readQuotedString();
      } else if (isNumericStart(character)) {
        return readNumeric();
      } else if (isSimpleStringStart(character)) {
        return readSimpleStringOrFunction();
      } else {
        throw new ConditionLexerError("Unexpected character: '" + character + "'");
      }
    }

    return null;
  }

  private Token readOperator() {
    char character = getChar();

    String op1 = Character.toString(character);
    String op2 = op1 + peekChar();

    if (OPERATOR_CHARACTERS.containsKey(op2)) {
      getChar();
      return new Token(OPERATOR_CHARACTERS.get(op2), op2);
    } else {
      return new Token(OPERATOR_CHARACTERS.get(op1), op1);
    }
  }

  private Token readSyntaxToken() {
    char character = getChar();
    return new Token(SYNTAX_CHARACTERS.get(character), Character.toString(character));
  }

  private Token readProperty() {
    StringBuilder value = new StringBuilder();
    value.append(getChar());

    char character = peekChar();
    if (character != '(') {
      throw new ConditionLexerError(
          String.format(
              "Invalid property reference. Expected '$' to be followed by '(', got '%c'",
              character));
    }

    int nestingLevel = 0;

    while ((character = getChar()) != END_OF_INPUT) {
      value.append(character);

      if (character == '(') {
        ++nestingLevel;
      } else if (character == ')') {
        --nestingLevel;
      }

      if (nestingLevel == 0) {
        break;
      }
    }

    if (nestingLevel > 0) {
      throw new ConditionLexerError(
          "Invalid property reference. Expected a closing ')', got end of input.");
    }

    return new Token(TokenType.PROPERTY, value.toString());
  }

  private Token readQuotedString() {
    StringBuilder value = new StringBuilder();
    boolean expandable = false;
    boolean foundEndQuote = false;

    char character;
    ++position;

    while ((character = getChar()) != END_OF_INPUT) {
      if (character == '\'') {
        foundEndQuote = true;
        break;
      }

      if (character == '$' && peekChar() == '(') {
        expandable = true;
      }

      value.append(character);
    }

    if (!foundEndQuote) {
      throw new ConditionLexerError(
          "Invalid quoted string. Expected a closing quote, got end of input.");
    }

    return new Token(TokenType.STRING, value.toString(), expandable);
  }

  private Token readNumeric() {
    if (data.length() > 2
        && data.charAt(position) == '0'
        && Character.toLowerCase(data.charAt(position + 1)) == 'x') {
      return readHexNumeric();
    } else {
      return readRegularNumeric();
    }
  }

  private Token readHexNumeric() {
    StringBuilder value = new StringBuilder();
    value.append(getChar());
    value.append(getChar());
    while (isHexDigit(peekChar())) {
      value.append(getChar());
    }
    return new Token(TokenType.NUMERIC, value.toString());
  }

  private Token readRegularNumeric() {
    StringBuilder value = new StringBuilder();
    char start = peekChar();
    if (start == '+' || start == '-') {
      value.append(getChar());
    }
    while (true) {
      while (Character.isDigit(peekChar())) {
        value.append(getChar());
      }
      if (peekChar() == '.') {
        value.append(getChar());
      } else {
        break;
      }
    }
    return new Token(TokenType.NUMERIC, value.toString());
  }

  private Token readSimpleStringOrFunction() {
    char character;
    StringBuilder value = new StringBuilder();

    while ((character = peekChar()) != END_OF_INPUT) {
      if (isSimpleStringChar(character)) {
        value.append(getChar());
      } else {
        break;
      }
    }

    String text = value.toString();
    if (text.equalsIgnoreCase("and")) {
      return new Token(TokenType.AND, text);
    } else if (text.equalsIgnoreCase("or")) {
      return new Token(TokenType.OR, text);
    } else {
      skipWhitespace();
      TokenType tokenType = (peekChar() == '(') ? TokenType.FUNCTION : TokenType.STRING;
      return new Token(tokenType, text);
    }
  }

  private static boolean isNumericStart(char character) {
    return character == '+' || character == '-' || character == '.' || Character.isDigit(character);
  }

  private static boolean isHexAlpha(char character) {
    return (character >= 'a' && character <= 'f') || (character >= 'A' && character <= 'F');
  }

  private static boolean isHexDigit(char character) {
    return Character.isDigit(character) || isHexAlpha(character);
  }

  private static boolean isSimpleStringStart(char character) {
    return Character.isAlphabetic(character) || character == '_';
  }

  private static boolean isSimpleStringChar(char character) {
    return isSimpleStringStart(character) || Character.isDigit(character);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy