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

com.igormaznitsa.jcp.expression.ExpressionTreeElement Maven / Gradle / Ivy

Go to download

Powerful multi-pass preprocessor to process directives situated in C-styled commentaries

There is a newer version: 7.1.2
Show newest version
/*
 * Copyright 2002-2019 Igor Maznitsa (http://www.igormaznitsa.com)
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.igormaznitsa.jcp.expression;

import com.igormaznitsa.jcp.exceptions.FilePositionInfo;
import com.igormaznitsa.jcp.exceptions.PreprocessorException;
import com.igormaznitsa.jcp.expression.functions.AbstractFunction;
import com.igormaznitsa.jcp.expression.operators.AbstractOperator;
import com.igormaznitsa.jcp.expression.operators.OperatorSUB;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * The class describes a wrapper around an expression item to be saved into an expression tree
 *
 * @author Igor Maznitsa ([email protected])
 */
public class ExpressionTreeElement {

  public static final ExpressionTreeElement EMPTY_SLOT = new ExpressionTreeElement();
  /**
   * Inside constant to be used for speed up some operations
   */
  private static final OperatorSUB OPERATOR_SUB = AbstractOperator.findForClass(OperatorSUB.class);
  /**
   * Empty array to avoid unnecessary operations
   */
  private static final ExpressionTreeElement[] EMPTY = new ExpressionTreeElement[0];
  /**
   * Contains the source string for the expression.
   */
  private final String sourceString;
  /**
   * Current include stack of the preprocessor to the source string.
   */
  private final FilePositionInfo[] includeStack;
  /**
   * The variable contains the wrapped expression item
   */
  private ExpressionItem savedItem;
  /**
   * The array contains links to the tree element children
   */
  private ExpressionTreeElement[] childElements;
  /**
   * The link to the parent element, if it is the tree root then it contains null
   */
  private ExpressionTreeElement parentTreeElement;
  /**
   * The priority of the tree element, it is very strongly used during tree sorting
   */
  private int priority;
  /**
   * Because I fill children sequentially, the variable contains the index of the first empty child slot
   */
  private int nextChildSlot = 0;

  private ExpressionTreeElement() {
    this.sourceString = "";
    this.includeStack = new FilePositionInfo[0];
  }

  /**
   * The constructor
   *
   * @param item         an expression item to be wrapped
   * @param callStack    current call stack
   * @param sourceString source string for the expression
   */
  ExpressionTreeElement(final ExpressionItem item, final FilePositionInfo[] callStack,
                        final String sourceString) {
    this.sourceString = sourceString;
    this.includeStack = callStack;

    if (item == null) {
      throw new PreprocessorException("[Expression]The item is null", this.sourceString,
          this.includeStack, null);
    }

    int arity = 0;
    if (item.getExpressionItemType() == ExpressionItemType.OPERATOR) {
      arity = ((AbstractOperator) item).getArity();
    } else if (item.getExpressionItemType() == ExpressionItemType.FUNCTION) {
      arity = ((AbstractFunction) item).getArity();
    }
    priority = item.getExpressionItemPriority().getPriority();
    this.savedItem = item;
    childElements = arity == 0 ? EMPTY : new ExpressionTreeElement[arity];
    Arrays.fill(this.childElements, EMPTY_SLOT);
  }

  /**
   * Allows to check that the element is EMPTY_SLOT
   *
   * @return true if the element is empty slot, false otherwise
   */
  public boolean isEmptySlot() {
    return EMPTY_SLOT == this;
  }

  private void assertNotEmptySlot() {
    if (isEmptySlot()) {
      throw new UnsupportedOperationException("Unsupported operation for empty slot");
    }
  }

  /**
   * Inside auxiliary function to set the maximum priority the the element
   */
  void makeMaxPriority() {
    priority = ExpressionItemPriority.VALUE.getPriority();
  }

  /**
   * Get the wrapped item
   *
   * @return the item to be wrapped by the object
   */

  public ExpressionItem getItem() {
    return this.savedItem;
  }

  /**
   * Get arity for the element (I mean possible children number)
   *
   * @return the arity, zero for elements without children
   */
  public int getArity() {
    return childElements.length;
  }

  /**
   * Get the parent for the element
   *
   * @return the parent for the element or null if the element is the tree root
   */

  public ExpressionTreeElement getParent() {
    return parentTreeElement;
  }

  /**
   * Get the current priority of the element
   *
   * @return the priority
   */
  public int getPriority() {
    return priority;
  }

  /**
   * Add a tree as new child and make the maximum priority for it
   *
   * @param tree a tree to be added as a child, must not be null
   * @return it returns this
   */

  public ExpressionTreeElement addSubTree(final ExpressionTree tree) {
    assertNotEmptySlot();

    final ExpressionTreeElement root = tree.getRoot();
    if (!root.isEmptySlot()) {
      root.makeMaxPriority();
      addElementToNextFreeSlot(root);
    }
    return this;
  }

  /**
   * It replaces a child element
   *
   * @param oldOne the old expression element to be replaced (must not be null)
   * @param newOne the new expression element to be used instead the old one (must not be null)
   * @return true if the element was found and replaced, else false
   */
  public boolean replaceElement(final ExpressionTreeElement oldOne,
                                final ExpressionTreeElement newOne) {
    assertNotEmptySlot();

    if (oldOne == null) {
      throw new PreprocessorException("[Expression]The old element is null", this.sourceString,
          this.includeStack, null);
    }

    if (newOne == null) {
      throw new PreprocessorException("[Expression]The new element is null", this.sourceString,
          this.includeStack, null);
    }

    boolean result = false;

    final ExpressionTreeElement[] children = childElements;
    final int len = children.length;

    for (int i = 0; i < len; i++) {
      if (children[i] == oldOne) {
        children[i] = newOne;
        newOne.parentTreeElement = this;
        result = true;
        break;
      }
    }
    return result;
  }

  /**
   * Get the child element for its index (the first is 0)
   *
   * @param index the index of the needed child
   * @return the child or EMPTY_SLOT
   * @throws ArrayIndexOutOfBoundsException it will be thrown if an impossible index is being used
   * @see #EMPTY_SLOT
   */

  public ExpressionTreeElement getChildForIndex(final int index) {
    assertNotEmptySlot();
    return this.childElements[index];
  }

  /**
   * Add tree element with sorting operation depends on priority of the elements
   *
   * @param element the element to be added, must not be null
   * @return the element which should be used as the last for the current tree
   */

  public ExpressionTreeElement addTreeElement(final ExpressionTreeElement element) {
    assertNotEmptySlot();
    Objects.requireNonNull(element, "The element is null");

    final int newElementPriority = element.getPriority();

    ExpressionTreeElement result = this;

    final ExpressionTreeElement parentTreeElement = this.parentTreeElement;

    final int currentPriority = getPriority();

    if (newElementPriority < currentPriority) {
      if (parentTreeElement == null) {
        element.addTreeElement(this);
        result = element;
      } else {
        result = parentTreeElement.addTreeElement(element);
      }
    } else if (newElementPriority == currentPriority) {
      if (parentTreeElement != null) {
        parentTreeElement.replaceElement(this, element);
      }
      if (element.nextChildSlot >= element.childElements.length) {
        throw new PreprocessorException(
            "[Expression]Can't process expression item, may be wrong number of arguments",
            this.sourceString, this.includeStack, null);
      }
      element.childElements[element.nextChildSlot] = this;
      element.nextChildSlot++;
      this.parentTreeElement = element;
      result = element;
    } else if (isFull()) {
      final int lastElementIndex = getArity() - 1;

      final ExpressionTreeElement lastElement = childElements[lastElementIndex];
      if (lastElement.getPriority() > newElementPriority) {
        element.addElementToNextFreeSlot(lastElement);
        childElements[lastElementIndex] = element;
        element.parentTreeElement = this;
        result = element;
      }

    } else {
      addElementToNextFreeSlot(element);
      result = element;
    }
    return result;
  }

  /**
   * It allows to check that all children slots have been filled
   *
   * @return true if there is not any free child slot else false
   */
  public boolean isFull() {
    return nextChildSlot >= childElements.length;
  }

  /**
   * It fills children slots from a list containing expression trees
   *
   * @param arguments the list containing trees to be used as children
   */
  public void fillArguments(final List arguments) {
    assertNotEmptySlot();

    if (arguments == null) {
      throw new PreprocessorException("[Expression]Argument list is null", this.sourceString,
          this.includeStack, null);
    }

    if (childElements.length != arguments.size()) {
      throw new PreprocessorException("Wrong argument list size", this.sourceString,
          this.includeStack, null);
    }

    int i = 0;
    for (ExpressionTree arg : arguments) {
      if (arg == null) {
        throw new PreprocessorException("[Expression]Argument [" + (i + 1) + "] is null",
            this.sourceString, this.includeStack, null);
      }

      if (!childElements[i].isEmptySlot()) {
        throw new PreprocessorException(
            "[Expression]Non-empty slot detected, it is possible that there is a program error, contact a developer please",
            this.sourceString, this.includeStack, null);
      }

      final ExpressionTreeElement root = arg.getRoot();
      if (root.isEmptySlot()) {
        throw new PreprocessorException("[Expression]Empty argument [" + (i + 1) + "] detected",
            this.sourceString, this.includeStack, null);
      }
      childElements[i] = root;
      root.parentTreeElement = this;

      i++;
    }
  }

  /**
   * Add an expression element into the next free child slot
   *
   * @param element an element to be added, must not be null
   */
  private void addElementToNextFreeSlot(final ExpressionTreeElement element) {
    if (element == null) {
      throw new PreprocessorException("[Expression]Element is null", this.sourceString,
          this.includeStack, null);
    }

    if (childElements.length == 0) {
      throw new PreprocessorException(
          "[Expression]Unexpected element, may be unknown function [" + savedItem.toString() + ']',
          this.sourceString, this.includeStack, null);
    } else if (isFull()) {
      throw new PreprocessorException(
          "[Expression]There is not any possibility to add new argument [" + savedItem.toString() +
              ']', this.sourceString, this.includeStack, null);
    } else {
      childElements[nextChildSlot++] = element;
    }
    element.parentTreeElement = this;
  }

  /**
   * Post-processing after the tree is formed, the unary minus operation will be optimized
   */
  public void postProcess() {
    if (!this.isEmptySlot()) {

      switch (savedItem.getExpressionItemType()) {
        case OPERATOR: {
          if (savedItem == OPERATOR_SUB) {
            if (!childElements[0].isEmptySlot() && childElements[1].isEmptySlot()) {
              final ExpressionTreeElement left = childElements[0];
              final ExpressionItem item = left.getItem();
              if (item.getExpressionItemType() == ExpressionItemType.VALUE) {
                final Value val = (Value) item;
                switch (val.getType()) {
                  case INT: {
                    childElements = EMPTY;
                    savedItem = Value.valueOf(-val.asLong());
                    makeMaxPriority();
                  }
                  break;
                  case FLOAT: {
                    childElements = EMPTY;
                    savedItem = Value.valueOf(0.0f - val.asFloat());
                    makeMaxPriority();
                  }
                  break;
                  default: {
                    if (!left.isEmptySlot()) {
                      left.postProcess();
                    }
                  }
                  break;
                }
              }
            } else {
              for (final ExpressionTreeElement element : childElements) {
                if (!element.isEmptySlot()) {
                  element.postProcess();
                }
              }
            }
          } else {
            for (final ExpressionTreeElement element : childElements) {
              if (!element.isEmptySlot()) {
                element.postProcess();
              }
            }
          }
        }
        break;
        case FUNCTION: {
          for (final ExpressionTreeElement element : childElements) {
            if (!element.isEmptySlot()) {
              element.postProcess();
            }
          }
        }
        break;
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy