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

com.squarespace.less.model.Condition Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2014 SQUARESPACE, Inc.
 *
 * Licensed 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.squarespace.less.model;

import static com.squarespace.less.core.LessUtils.safeEquals;
import static com.squarespace.less.model.Operator.EQUAL;
import static com.squarespace.less.model.Operator.GREATER_THAN;
import static com.squarespace.less.model.Operator.GREATER_THAN_OR_EQUAL;
import static com.squarespace.less.model.Operator.LESS_THAN;
import static com.squarespace.less.model.Operator.LESS_THAN_OR_EQUAL;
import static com.squarespace.less.model.Operator.NOT_EQUAL;

import com.squarespace.less.LessException;
import com.squarespace.less.core.Buffer;
import com.squarespace.less.core.Constants;
import com.squarespace.less.core.ExecuteErrorMaker;
import com.squarespace.less.core.LessInternalException;
import com.squarespace.less.exec.ExecEnv;


/**
 * Represents one conditional clause in a {@link Guard}.
 */
public class Condition implements Node {

  /**
   * Operator to apply to the left and right operands.
   */
  protected final Operator operator;

  /**
   * Left operand.
   */
  protected final Node left;

  /**
   * Right operand.
   */
  protected final Node right;

  /**
   * Whether to negate the result of the evaluation.
   */
  protected final boolean negate;

  /**
   * Constructs a conditional clause, applying the boolean operator to the left
   * and right operands.  Negates the result if the {@code negate} flag is set.
   */
  public Condition(Operator operator, Node left, Node right, boolean negate) {
    if (operator == null || left == null || right == null) {
      throw new LessInternalException("Serious error: operator/operands cannot be null.");
    }
    this.operator = operator;
    this.left = left;
    this.right = right;
    this.negate = negate;
  }

  /**
   * Returns the operator.
   */
  public Operator operator() {
    return operator;
  }

  /**
   * Returns the left operand.
   */
  public Node left() {
    return left;
  }

  /**
   * Returns the right operand.
   */
  public Node right() {
    return right;
  }

  public boolean negate() {
    return negate;
  }

  /**
   * See {@link Node#needsEval()}
   */
  @Override
  public boolean needsEval() {
    return true;
  }

  /**
   * See {@link Node#eval(ExecEnv)}
   */
  @Override
  public Node eval(ExecEnv env) throws LessException {
    boolean result = negate ? !compare(env) : compare(env);
    return result ? Constants.TRUE : Constants.FALSE;
  }

  /**
   * See {@link Node#type()}
   */
  @Override
  public NodeType type() {
    return NodeType.CONDITION;
  }

  /**
   * See {@link Node#repr()}
   */
  @Override
  public void repr(Buffer buf) {
    if (negate) {
      buf.append("not ");
    }
    boolean nested = (left instanceof Condition) || (right instanceof Condition);
    if (!nested) {
      buf.append('(');
    }
    left.repr(buf);
    buf.append(' ').append(operator.toString()).append(' ');
    right.repr(buf);
    if (!nested) {
      buf.append(')');
    }
  }

  /**
   * See {@link Node#modelRepr(Buffer)}
   */
  @Override
  public void modelRepr(Buffer buf) {
    typeRepr(buf);
    posRepr(buf);
    buf.append(' ').append(operator.toString());
    if (negate) {
      buf.append(" [negate]");
    }
    buf.append('\n').incrIndent().indent();
    left.modelRepr(buf);
    buf.append('\n').indent();
    right.modelRepr(buf);
    buf.append('\n').decrIndent();
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof Condition) {
      Condition other = (Condition)obj;
      boolean res = operator == other.operator
          && negate == other.negate
          && safeEquals(left, other.left)
          && safeEquals(right, other.right);
      return res;
    }
    return false;
  }

  @Override
  public String toString() {
    return ModelUtils.toString(this);
  }

  @Override
  public int hashCode() {
    return ModelUtils.notHashable();
  }

  /**
   * Executes the comparison of the operands.
  */
  private boolean compare(ExecEnv env) throws LessException {
    Node op0 = left.needsEval() ? left.eval(env) : left;
    Node op1 = right.needsEval() ? right.eval(env) : right;

    switch (operator) {
      case ADD:
      case DIVIDE:
      case MULTIPLY:
      case SUBTRACT:
        throw new LessException(ExecuteErrorMaker.expectedBoolOp(operator));

      case AND:
        return conjunction(env, op0, op1);

      case OR:
        return disjunction(env, op0, op1);

      default:
        break;
    }

    NodeType type = op0.type();
    int result = -1;
    switch (type) {
      case ANONYMOUS:
        result = compare(env, (Anonymous)op0, op1);
        break;

      case COLOR:
        result = compare((BaseColor)op0, op1);
        break;

      case DIMENSION:
        result = compare((Dimension)op0, op1);
        break;

      case KEYWORD:
      case TRUE:
      case FALSE:
        result = compare((Keyword)op0, op1);
        break;

      case QUOTED:
        result = compare(env, (Quoted)op0, op1);
        break;

      default:
        throw new LessException(ExecuteErrorMaker.uncomparableType(type));
    }

    switch (result) {
      case -1:
        return operator == LESS_THAN || operator == LESS_THAN_OR_EQUAL || operator == NOT_EQUAL;
      case 0:
        return operator == EQUAL || operator == LESS_THAN_OR_EQUAL || operator == GREATER_THAN_OR_EQUAL;
      case 1:
        return operator == GREATER_THAN || operator == GREATER_THAN_OR_EQUAL || operator == NOT_EQUAL;
      default:
        throw new LessInternalException("Serious error: comparison functions must return -1, 0, or 1. Got " + result);
    }
  }

  /**
   * Logical AND of the two operands.
   */
  private boolean conjunction(ExecEnv env, Node left, Node right) throws LessException {
    return truthValue(env, left) && truthValue(env, right);
  }

  /**
   * Logical OR of the two operands.
   */
  private boolean disjunction(ExecEnv env, Node left, Node right) throws LessException {
    return truthValue(env, left) || truthValue(env, right);
  }

  /**
   * Compares an {@link Anonymous} node to another node.
   */
  private int compare(ExecEnv env, Anonymous anon, Node arg) throws LessException {
    String value = env.context().render(arg);
    return anon.value().compareTo(value);
  }

  /**
   * Maps node types to a truth value based on LESS notion of "truthiness".
   */
  private boolean truthValue(ExecEnv env, Node node) throws LessException {
    switch (node.type()) {

      case ANONYMOUS:
      case KEYWORD:
      case QUOTED:
        return "true".equals(render(env, node));

      case TRUE:
        return true;

      default:
        return false;
    }
  }

  /**
   * Compares a {@link BaseColor} to another node.
   */
  private int compare(BaseColor color, Node arg) throws LessException {
    if (arg instanceof Keyword) {
      Keyword kwd = (Keyword)arg;
      RGBColor tmp = RGBColor.fromName(kwd.value());
      if (tmp != null) {
        arg = tmp;
      }
    }
    if (!(arg instanceof BaseColor)) {
      return -1;
    }
    RGBColor color0 = color.toRGB();
    RGBColor color1 = ((BaseColor)arg).toRGB();
    return color0.red() == color1.red()
        && color0.green() == color1.green()
        && color0.blue() == color1.blue()
        && color0.alpha() == color1.alpha() ? 0 : -1;
  }

  /**
   * Compares a {@link Dimension} to another node.
   */
  private int compare(Dimension dim0, Node arg) throws LessException {
    if (arg instanceof Dimension) {
      Dimension dim1 = (Dimension)arg;
      double value0 = dim0.value();
      Unit unit0 = dim0.unit();
      Unit unit1 = dim1.unit();
      unit0 = (unit0 == Unit.PERCENTAGE ? null : unit0);
      unit1 = (unit1 == Unit.PERCENTAGE ? null : unit1);

      double factor = 1.0;
      double scaled = dim1.value();
      if (dim0.unit() != dim1.unit()) {
        factor = UnitConversions.factor(unit1, unit0);
        if (factor == 0.0) {
          return -1;
        }
        scaled *= factor;
      }
      return value0 < scaled ? -1 : (value0 > scaled ? 1 : 0);
    }
    return -1;
  }

  /**
   * Compares a {@link Keyword} to another node.
   */
  private int compare(Keyword keyword, Node arg) throws LessException {
    if (arg instanceof Keyword) {
      return compareTo(keyword.value(), ((Keyword)arg).value());
    }
    return -1;
  }

  /**
   * Compares a {@link Quoted} to another node.
  */
  private int compare(ExecEnv env, Quoted quoted, Node arg) throws LessException {
    return compareTo(render(env, quoted), render(env, arg));
  }

  /**
   * Renders a node to get its string representation, for comparison purposes.
   */
  private String render(ExecEnv env, Node arg) throws LessException {
    if (arg instanceof Anonymous) {
      return ((Anonymous)arg).value();
    } else if (arg instanceof Keyword) {
      return ((Keyword)arg).value();
    }
    return env.context().render(arg);
  }

  /**
   * Compares two strings.
   */
  private int compareTo(String left, String right) {
    int res = left.compareTo(right);
    return res < 0 ? -1 : (res > 0) ? 1 : 0;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy