class Asciidoctor::PreprocessorReader

def preprocess_conditional_directive keyword, target, delimiter, text

Returns a Boolean indicating whether the cursor should be advanced

ifndef directives, and for the conditional expression for the ifeval directive.
Used for a single-line conditional block in the case of the ifdef or
text - The text associated with this directive (occurring between the square brackets)
can be defined or undefined.
attributes must be defined or undefined, ',' means any of the attributes
delimiter - The conditional delimiter for multiple attributes ('+' means all
used in the condition (blank in the case of the ifeval directive)
target - The target, which is the name of one or more attributes that are
keyword - The conditional inclusion directive (ifdef, ifndef, ifeval, endif)

found.
preprocessing recursively until the next line of available content is
not skipping, mark whether the condition is satisfied and continue
open and close delimiters of any nested conditional blocks. If Reader is
the cursor. If Reader is currently skipping content, then simply track the
Preprocess the conditional directive (ifdef, ifndef, ifeval, endif) under

Internal: Preprocess the directive to conditionally include or exclude content.
def preprocess_conditional_directive keyword, target, delimiter, text
  # attributes are case insensitive
  target = target.downcase unless (no_target = target.empty?)
  if keyword == 'endif'
    if text
      logger.error message_with_context %(malformed preprocessor directive - text not permitted: endif::#{target}[#{text}]), source_location: cursor
    elsif @conditional_stack.empty?
      logger.error message_with_context %(unmatched preprocessor directive: endif::#{target}[]), source_location: cursor
    elsif no_target || target == (pair = @conditional_stack[-1])[:target]
      @conditional_stack.pop
      @skipping = @conditional_stack.empty? ? false : @conditional_stack[-1][:skipping]
    else
      logger.error message_with_context %(mismatched preprocessor directive: endif::#{target}[], expected endif::#{pair[:target]}[]), source_location: cursor
    end
    return true
  elsif @skipping
    skip = false
  else
    # QUESTION any way to wrap ifdef & ifndef logic up together?
    case keyword
    when 'ifdef'
      if no_target
        logger.error message_with_context %(malformed preprocessor directive - missing target: ifdef::[#{text}]), source_location: cursor
        return true
      end
      case delimiter
      when ','
        # skip if no attribute is defined
        skip = target.split(',', -1).none? {|name| @document.attributes.key? name }
      when '+'
        # skip if any attribute is undefined
        skip = target.split('+', -1).any? {|name| !@document.attributes.key? name }
      else
        # if the attribute is undefined, then skip
        skip = !@document.attributes.key?(target)
      end
    when 'ifndef'
      if no_target
        logger.error message_with_context %(malformed preprocessor directive - missing target: ifndef::[#{text}]), source_location: cursor
        return true
      end
      case delimiter
      when ','
        # skip if any attribute is defined
        skip = target.split(',', -1).any? {|name| @document.attributes.key? name }
      when '+'
        # skip if all attributes are defined
        skip = target.split('+', -1).all? {|name| @document.attributes.key? name }
      else
        # if the attribute is defined, then skip
        skip = @document.attributes.key?(target)
      end
    when 'ifeval'
      if no_target
        # the text in brackets must match a conditional expression
        if text && EvalExpressionRx =~ text.strip
          # NOTE assignments must happen before call to resolve_expr_val for compatibility with Opal
          lhs = $1
          # regex enforces a restricted set of math-related operations (==, !=, <=, >=, <, >)
          op = $2
          rhs = $3
          skip = ((resolve_expr_val lhs).send op, (resolve_expr_val rhs)) ? false : true rescue true
        else
          logger.error message_with_context %(malformed preprocessor directive - #{text ? 'invalid expression' : 'missing expression'}: ifeval::[#{text}]), source_location: cursor
          return true
        end
      else
        logger.error message_with_context %(malformed preprocessor directive - target not permitted: ifeval::#{target}[#{text}]), source_location: cursor
        return true
      end
    end
  end
  # conditional inclusion block
  if keyword == 'ifeval' || !text
    @skipping = true if skip
    @conditional_stack << { target: target, skip: skip, skipping: @skipping }
  # single line conditional inclusion
  else
    unless @skipping || skip
      replace_next_line text.rstrip
      # HACK push dummy line to stand in for the opening conditional directive that's subsequently dropped
      unshift ''
      # NOTE force line to be processed again if it looks like an include directive
      # QUESTION should we just call preprocess_include_directive here?
      @look_ahead -= 1 if text.start_with? 'include::'
    end
  end
  true
end