class TaskJuggler::LogicalOperation

A LogicalOperation is the basic building block for a LogicalExpression. A logical operation has one or two operands and an operator. The operands can be LogicalOperation objects, fixed values or references to project data. The LogicalOperation can be evaluated in a certain context. This contexts determines the actual values of the project data references. The evaluation is done by calling LogicalOperation#eval. The result must be of a type that responds to all the operators that are used in the eval method.

Attributes

operand1[R]
operand2[RW]
operator[RW]

Public Class Methods

new(opnd1, operator = nil, opnd2 = nil) click to toggle source

Create a new LogicalOperation object. opnd1 is the mandatory operand. The @operand2 and the @operator can be set later.

# File lib/taskjuggler/LogicalOperation.rb, line 36
def initialize(opnd1, operator = nil, opnd2 = nil)
  @operand1 = opnd1
  @operand2 = opnd2
  @operator = operator
end

Public Instance Methods

eval(expr) click to toggle source

Evaluate the expression in a given context represented by expr of type LogicalExpression. The result must be of a type that responds to all the operators of this function.

# File lib/taskjuggler/LogicalOperation.rb, line 45
def eval(expr)
  case @operator
  when nil
    if @operand1.respond_to?(:eval)
      # An operand can be a fixed value or another term. This could be a
      # LogicalOperation, LogicalFunction or anything else that provides
      # an appropriate eval() method.
      return @operand1.eval(expr)
    else
      return @operand1
    end
  when '~'
    return !coerceBoolean(@operand1.eval(expr), expr)
  when '>', '>=', '=', '<', '<=', '!='
    # Evaluate the operation for all 2 operand operations that can be
    # either interpreted as date, numbers or Strings.
    opnd1 = @operand1.eval(expr)
    opnd2 = @operand2.eval(expr)
    if opnd1.is_a?(TjTime)
      res= evalBinaryOperation(opnd1, operator, opnd2) do |o|
        coerceTime(o, expr)
      end
      return res
    elsif opnd1.is_a?(Integer) || opnd1.is_a?(Float)
      return evalBinaryOperation(opnd1, operator, opnd2) do |o|
        coerceNumber(o, expr)
      end
    elsif opnd1.is_a?(RichTextIntermediate)
      return evalBinaryOperation(opnd1.to_s, operator, opnd2) do |o|
        coerceString(o, expr)
      end
    elsif opnd1.is_a?(String)
      return evalBinaryOperation(opnd1, operator, opnd2) do |o|
        coerceString(o, expr)
      end
    else
      expr.error("First operand of a binary operation must be a date, " +
                 "a number or a string: #{opnd1.class}")
    end
  when '&'
    return coerceBoolean(@operand1.eval(expr), expr) &&
           coerceBoolean(@operand2.eval(expr), expr)
  when '|'
    return coerceBoolean(@operand1.eval(expr), expr) ||
           coerceBoolean(@operand2.eval(expr), expr)
  else
    expr.error("Unknown operator #{@operator} in logical expression")
  end
end
to_s(query) click to toggle source

Convert the operation into a textual representation.

# File lib/taskjuggler/LogicalOperation.rb, line 96
def to_s(query)
  if @operator.nil?
    operand_to_s(@operand1, query)
  elsif @operand2.nil?
    @operator + operand_to_s(@operand1, query)
  else
    "(#{operand_to_s(@operand1, query)} #{@operator} " +
    "#{operand_to_s(@operand2, query)})"
  end
end

Private Instance Methods

coerceBoolean(val, expr) click to toggle source

Force the val into a boolean value.

# File lib/taskjuggler/LogicalOperation.rb, line 143
def coerceBoolean(val, expr)
  # First the obvious ones.
  return val if val.class == TrueClass || val.class == FalseClass
  # An empty String means false, else true.
  return !val.empty? if val.is_a?(String)
  # In TJP logic 'non 0' means false.
  return val != 0 if val.is_a?(Integer)

  expr.error("Operand #{val} can't be evaluated to true or false.")
end
coerceNumber(val, expr) click to toggle source

Force the val into a number. In case this fails, an exception is raised.

# File lib/taskjuggler/LogicalOperation.rb, line 155
def coerceNumber(val, expr)
  unless val.is_a?(Integer) || val.is_a?(Float)
    expr.error("Operand #{val} of type #{val.class} must be a number.")
  end
  val
end
coerceString(val, expr) click to toggle source

Force the val into a String. In case this fails, an exception is raised.

# File lib/taskjuggler/LogicalOperation.rb, line 163
def coerceString(val, expr)
  unless val.respond_to?('to_s')
    expr.error("Operand #{val} of type #{val.class} can't be converted " +
               "into a string")
  end
  val
end
coerceTime(val, expr) click to toggle source

Force the val into a String. In case this fails, an exception is raised.

# File lib/taskjuggler/LogicalOperation.rb, line 172
def coerceTime(val, expr)
  unless val.is_a?(TjTime)
    expr.error("Operand #{val} of type #{val.class} can't be converted " +
               "into a date")
  end
  val
end
evalBinaryOperation(opnd1, operator, opnd2) { |opnd1| ... } click to toggle source

We need to do binary operator evaluation with various coerce functions. This function does the evaluation of opnd1 and opnd2 with the operation specified by operator. The operands are first coerced into the proper format by calling the block.

# File lib/taskjuggler/LogicalOperation.rb, line 123
def evalBinaryOperation(opnd1, operator, opnd2)
  case operator
  when '>'
    return yield(opnd1) > yield(opnd2)
  when '>='
    return yield(opnd1) >= yield(opnd2)
  when '='
    return yield(opnd1) == yield(opnd2)
  when '<'
    return yield(opnd1) < yield(opnd2)
  when '<='
    return yield(opnd1) <= yield(opnd2)
  when '!='
    return yield(opnd1) != yield(opnd2)
  else
    raise "Operator error"
  end
end
operand_to_s(operand, query) click to toggle source
# File lib/taskjuggler/LogicalOperation.rb, line 109
def operand_to_s(operand, query)
  if operand.is_a?(LogicalOperation)
    operand.to_s(query)
  elsif operand.is_a?(String)
    "'#{operand}'"
  else
    operand.to_s
  end
end