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

au.com.integradev.delphi.preprocessor.directive.CompilerDirectiveParserImpl 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.preprocessor.directive;

import static au.com.integradev.delphi.preprocessor.directive.CompilerDirectiveParserImpl.DirectiveBracketType.CURLY;
import static au.com.integradev.delphi.preprocessor.directive.CompilerDirectiveParserImpl.DirectiveBracketType.PAREN;

import au.com.integradev.delphi.compiler.Platform;
import au.com.integradev.delphi.preprocessor.TextBlockLineEndingModeRegistry;
import au.com.integradev.delphi.preprocessor.directive.expression.Expression;
import au.com.integradev.delphi.preprocessor.directive.expression.ExpressionLexer;
import au.com.integradev.delphi.preprocessor.directive.expression.ExpressionLexer.ExpressionLexerError;
import au.com.integradev.delphi.preprocessor.directive.expression.ExpressionParser;
import au.com.integradev.delphi.preprocessor.directive.expression.ExpressionParser.ExpressionParserError;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.sonar.plugins.communitydelphi.api.directive.CompilerDirective;
import org.sonar.plugins.communitydelphi.api.directive.CompilerDirectiveParser;
import org.sonar.plugins.communitydelphi.api.directive.ConditionalDirective.ConditionalKind;
import org.sonar.plugins.communitydelphi.api.directive.ParameterDirective.ParameterKind;
import org.sonar.plugins.communitydelphi.api.directive.ResourceDirective;
import org.sonar.plugins.communitydelphi.api.directive.SwitchDirective.SwitchKind;
import org.sonar.plugins.communitydelphi.api.directive.TextBlockDirective;
import org.sonar.plugins.communitydelphi.api.directive.TextBlockDirective.LineEndingKind;
import org.sonar.plugins.communitydelphi.api.directive.WarnDirective;
import org.sonar.plugins.communitydelphi.api.directive.WarnDirective.WarnParameterValue;
import org.sonar.plugins.communitydelphi.api.token.DelphiToken;

public class CompilerDirectiveParserImpl implements CompilerDirectiveParser {
  private static final ExpressionLexer EXPRESSION_LEXER = new ExpressionLexer();

  private static final char END_OF_INPUT = '\0';

  enum DirectiveBracketType {
    CURLY,
    PAREN
  }

  private final Platform platform;
  private final TextBlockLineEndingModeRegistry textBlockLineEndingModeRegistry;

  // Parser state
  private String data;
  private int position;

  // Current directive state
  private DelphiToken token;
  private DirectiveBracketType directiveBracketType;

  public CompilerDirectiveParserImpl(
      Platform platform, TextBlockLineEndingModeRegistry textBlockLineEndingModeRegistry) {
    this.platform = platform;
    this.textBlockLineEndingModeRegistry = textBlockLineEndingModeRegistry;
  }

  @Override
  public Optional parse(DelphiToken token) {
    this.data = token.getImage();
    this.position = 0;

    this.token = token;
    this.directiveBracketType = (currentChar() == '{') ? CURLY : PAREN;

    if (directiveBracketType == CURLY) {
      position += 2;
      // Jump ahead of the "{$"
    }

    if (directiveBracketType == PAREN) {
      position += 3;
      // Jump ahead of the "(*$"
    }

    return Optional.ofNullable(createDirective(readName()));
  }

  private CompilerDirective createDirective(String name) {
    Optional switchKind = SwitchKind.find(name);
    Optional shortSwitchValue =
        (name.length() == 1) ? readShortSwitchValue() : Optional.empty();

    if (switchKind.isPresent()) {
      Optional switchValue = shortSwitchValue.or(this::readLongSwitchValue);
      if (switchValue.isPresent()) {
        return new SwitchDirectiveImpl(token, switchKind.get(), switchValue.get());
      } else if (isMinEnumSize(switchKind.get())) {
        return new SwitchDirectiveImpl(token, switchKind.get(), true);
      }
    }

    if (shortSwitchValue.isPresent()) {
      // The syntax indicates that this can only be a switch.
      return null;
    }

    Optional parameterKind = ParameterKind.find(name, platform);
    if (parameterKind.isPresent()) {
      ParameterKind kind = parameterKind.get();
      switch (kind) {
        case DEFINE:
          return new DefineDirectiveImpl(token, readDirectiveParameter());
        case UNDEF:
          return new UndefineDirectiveImpl(token, readDirectiveParameter());
        case INCLUDE:
          return new IncludeDirectiveImpl(token, readDirectiveParameter());
        case RESOURCE:
          return createResourceDirective();
        case WARN:
          return createWarnDirective();
        case TEXTBLOCK:
          return createTextBlockDirective();
        default:
          return new ParameterDirectiveImpl(token, kind);
      }
    }

    Optional conditionalKind = ConditionalKind.find(name);
    if (conditionalKind.isPresent()) {
      ConditionalKind kind = conditionalKind.get();
      switch (kind) {
        case IFDEF:
          return new IfDefDirectiveImpl(token, readDirectiveParameter());

        case IFNDEF:
          return new IfnDefDirectiveImpl(token, readDirectiveParameter());

        case IFOPT:
          return createIfOptDirective();

        case IF:
          return new IfDirective(token, readExpression());

        case ELSEIF:
          return new ElseIfDirective(token, readExpression());

        case ELSE:
          return new ElseDirective(token);

        case ENDIF:
          return new EndIfDirective(token);

        case IFEND:
          return new IfEndDirective(token);
      }
    }

    return null;
  }

  private static boolean isMinEnumSize(SwitchKind switchKind) {
    return switchKind == SwitchKind.MINENUMSIZE1
        || switchKind == SwitchKind.MINENUMSIZE2
        || switchKind == SwitchKind.MINENUMSIZE4;
  }

  private ResourceDirective createResourceDirective() {
    String resourceFile = readDirectiveParameter();
    String resourceScriptFile = null;
    List predicates = new ArrayList<>();

    char character = currentChar();
    while (Character.isWhitespace(character)) {
      character = nextChar();
    }

    if (character == '\'') {
      resourceScriptFile = StringUtils.stripToNull(readDirectiveParameter());
    } else {
      while (true) {
        String predicate = StringUtils.stripToNull(readDirectiveParameter());
        if (predicate == null) {
          break;
        }
        predicates.add(predicate);
      }
    }

    return new ResourceDirectiveImpl(token, resourceFile, resourceScriptFile, predicates);
  }

  private WarnDirective createWarnDirective() {
    String identifier = readDirectiveParameter();
    String parameter = readDirectiveParameter();
    WarnParameterValue value = EnumUtils.getEnumIgnoreCase(WarnParameterValue.class, parameter);
    if (value == null) {
      return null;
    }
    return new WarnDirectiveImpl(token, identifier, value);
  }

  private TextBlockDirective createTextBlockDirective() {
    String parameter = readDirectiveParameter();
    LineEndingKind lineEndingKind = EnumUtils.getEnumIgnoreCase(LineEndingKind.class, parameter);
    if (lineEndingKind == null) {
      return null;
    }
    return new TextBlockDirectiveImpl(token, lineEndingKind);
  }

  private IfOptDirective createIfOptDirective() {
    char character = currentChar();
    while (Character.isWhitespace(character)) {
      character = nextChar();
    }
    String switchName = readName();
    if (switchName.length() == 1) {
      Optional switchKind = SwitchKind.find(switchName);
      if (switchKind.isPresent()) {
        Optional switchValue = readShortSwitchValue();
        if (switchValue.isPresent()) {
          return new IfOptDirective(token, switchKind.get(), switchValue.get());
        }
      }
    }
    return null;
  }

  private String readName() {
    StringBuilder name = new StringBuilder();
    char character = currentChar();

    while ((character >= 'a' && character <= 'z')
        || (character >= 'A' && character <= 'Z')
        || Character.isDigit(character)
        || character == '_') {
      name.append(character);
      character = nextChar();
    }

    return name.toString();
  }

  private Optional readShortSwitchValue() {
    char character = currentChar();
    if (character == '+' || character == '-') {
      nextChar();
      return Optional.of(character == '+');
    }
    return Optional.empty();
  }

  private Optional readLongSwitchValue() {
    int oldPosition = position;
    String item = readDirectiveParameter();
    switch (item.toUpperCase()) {
      case "ON":
        return Optional.of(true);
      case "OFF":
        return Optional.of(false);
      default:
        position = oldPosition;
        return Optional.empty();
    }
  }

  private String readDirectiveParameter() {
    StringBuilder item = new StringBuilder();
    char character = currentChar();
    boolean insideQuote = false;

    while (Character.isWhitespace(character)) {
      character = nextChar();
    }

    while (!(Character.isWhitespace(character) && !insideQuote) && !isEndOfDirective(character)) {
      if (character == '\'') {
        insideQuote = !insideQuote;
        character = nextChar();
        continue;
      }

      item.append(character);
      character = nextChar();
    }

    return item.toString();
  }

  private Expression readExpression() {
    StringBuilder input = new StringBuilder();
    char character = currentChar();

    while (!isEndOfDirective(character)) {
      input.append(character);
      character = nextChar();
    }

    try {
      var tokens = EXPRESSION_LEXER.lex(input.toString());
      return expressionParser().parse(tokens);
    } catch (ExpressionLexerError | ExpressionParserError e) {
      throw new CompilerDirectiveParserError(e, token);
    }
  }

  private ExpressionParser expressionParser() {
    int index = token.getIndex();
    return new ExpressionParser(textBlockLineEndingModeRegistry.getLineEndingMode(index));
  }

  private boolean isEndOfDirective(char character) {
    boolean result = false;

    if (directiveBracketType == CURLY) {
      result = character == '}' && peekChar() == END_OF_INPUT;
    }

    if (directiveBracketType == PAREN) {
      result = character == '*' && peekChar() == ')' && peekChar(1) == END_OF_INPUT;
    }

    return result;
  }

  private char currentChar() {
    return getChar(position);
  }

  private char nextChar() {
    ++position;
    return getChar(position);
  }

  private char peekChar() {
    return peekChar(0);
  }

  private char peekChar(int offset) {
    return getChar(position + offset + 1);
  }

  private char getChar(int position) {
    return (position < data.length()) ? data.charAt(position) : END_OF_INPUT;
  }

  static final class CompilerDirectiveParserError extends RuntimeException {
    private CompilerDirectiveParserError(Exception e, DelphiToken token) {
      super(e.getMessage() + " ", e);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy