class AdLint::Cpp::FunctionLikeMacro

Attributes

parameter_names[R]

Public Class Methods

new(define_line) click to toggle source
Calls superclass method AdLint::Cpp::Macro::new
# File lib/adlint/cpp/macro.rb, line 100
def initialize(define_line)
  super
  if params = define_line.identifier_list
    @parameter_names = params.identifiers.map { |tok| tok.value }
  else
    @parameter_names = []
  end
end

Public Instance Methods

expand(toks, macro_tbl, repl_ctxt) click to toggle source
Calls superclass method AdLint::Cpp::Macro#expand
# File lib/adlint/cpp/macro.rb, line 124
def expand(toks, macro_tbl, repl_ctxt)
  super

  args, * = parse_arguments(toks, 1)
  args = [] if @parameter_names.empty?
  args_hash =
    @parameter_names.zip(args).each_with_object({}) { |(param, arg), hash|
      hash[param] = arg
    }

  rslt_toks = expand_replacement_list(args_hash, toks.first.location,
                                      macro_tbl, repl_ctxt)
  macro_tbl.notify_function_like_macro_replacement(self, toks, args,
                                                   rslt_toks)
  rslt_toks
end
function_like?() click to toggle source
# File lib/adlint/cpp/macro.rb, line 141
def function_like?; true end
replaceable_size(toks) click to toggle source
# File lib/adlint/cpp/macro.rb, line 111
def replaceable_size(toks)
  if name.value == toks.first.value
    args, idx = parse_arguments(toks, 1)
    case
    when args && @parameter_names.empty?
      return idx + 1
    when args && @parameter_names.size >= args.size
      return idx + 1
    end
  end
  0
end

Private Instance Methods

concat_with_last_token(arg_toks, expansion_loc, rslt_toks, macro_tbl) click to toggle source
# File lib/adlint/cpp/macro.rb, line 313
def concat_with_last_token(arg_toks, expansion_loc, rslt_toks, macro_tbl)
  # NOTE: The ISO C99 standard says;
  #
  # 6.10.3.3 The ## operator
  #
  # Constraints
  #
  # 1 A ## preprocessing token shall not occur at the beginning or at the
  #   end of a replacement list for either form of macro definition.
  #
  # Semantics
  #
  # 2 If, in the replacement list of a function-form macro, a parameter is
  #   immediately preceded or followed by a ## preprocessing token, the
  #   parameter is replaced by the corresponding argument's preprocessing
  #   token sequence; however, if an argument consists of no preprocessing
  #   tokens, the parameter is replaced by a placemarker preprocessing
  #   token instead.
  #
  # 3 For both object-like and function-like macro invocations, before the
  #   replacement list is reexamined for more macro names to replace, each
  #   instance of a ## preprocessing token in the replacement list (not
  #   from an argument) is deleted and the preceding preprocessing token is
  #   concatenated with the following preprocessing token.  Placemarker
  #   preprocessing tokens are handled specially: concatenation of two
  #   placemarkers results in a single placemarker preprocessing token, and
  #   concatenation of a placemarker with a non-placemarker preprocessing
  #   token results in the non-placemarker preprocessing token.  If the
  #   result is not a valid preprocessing token, the behavior is undefined.
  #   The resulting token is available for further macro replacement.  The
  #   order of evaluation of ## operators is unspecified.

  if lhs = rslt_toks.pop
    unless arg_toks.empty?
      # NOTE: To avoid syntax error when the concatenated token can be
      #       retokenize to two or more tokens.
      unlexed_arg = unlex_tokens(arg_toks)
      new_toks = StringToPPTokensLexer.new(lhs.value + unlexed_arg).execute
      new_toks.map! do |tok|
        ReplacedToken.new(tok.type, tok.value, expansion_loc,
                          tok.type_hint, false)
      end
      rslt_toks.concat(new_toks)

      macro_tbl.notify_sharpsharp_operator_evaled(
        lhs, Token.new(:MACRO_ARG, unlexed_arg, arg_toks.first.location),
        new_toks)
    else
      new_toks = [ReplacedToken.new(lhs.type, lhs.value, expansion_loc,
                                    lhs.type_hint, false)]
      rslt_toks.concat(new_toks)
    end
  end
end
expand_replacement_list(args, loc, macro_tbl, repl_ctxt) click to toggle source
# File lib/adlint/cpp/macro.rb, line 198
def expand_replacement_list(args, loc, macro_tbl, repl_ctxt)
  unless repl_list = self.replacement_list
    return []
  end

  rslt_toks = []
  idx = 0
  while cur_tok = repl_list.tokens[idx]
    nxt_tok = repl_list.tokens[idx + 1]

    case
    when arg = args[cur_tok.value]
      substitute_argument(cur_tok, nxt_tok, arg, loc, rslt_toks, macro_tbl,
                          repl_ctxt)
    when cur_tok.value == "#"
      if nxt_tok
        tok = stringize_argument(args[nxt_tok.value], loc, macro_tbl)
        rslt_toks.push(tok)
        idx += 1
      end
    when cur_tok.value == "##" && nxt_tok.value == "#"
      if nxt_nxt_tok = repl_list.tokens[idx + 2]
        tok = stringize_argument(args[nxt_nxt_tok.value], loc, macro_tbl)
        concat_with_last_token([tok], loc, rslt_toks, macro_tbl)
        idx += 2
      end
    when cur_tok.value == "##"
      if nxt_tok and arg = args[nxt_tok.value]
        concat_with_last_token(arg, loc, rslt_toks, macro_tbl)
      else
        concat_with_last_token([nxt_tok], loc, rslt_toks, macro_tbl)
      end
      idx += 1
    else
      rslt_toks.push(ReplacedToken.new(cur_tok.type, cur_tok.value, loc,
                                       cur_tok.type_hint, false))
    end
    idx += 1
  end
  rslt_toks
end
parse_arguments(toks, idx) click to toggle source
# File lib/adlint/cpp/macro.rb, line 144
def parse_arguments(toks, idx)
  while tok = toks[idx]
    case
    when tok.type == :NEW_LINE
      idx += 1
    when tok.value == "("
      idx += 1
      break
    else
      return nil, idx
    end
  end
  return nil, idx unless tok

  args = []
  loop do
    arg, idx, lst = parse_one_argument(toks, idx)
    args.push(arg)
    break if lst
  end
  return args, idx
end
parse_one_argument(toks, idx) click to toggle source
# File lib/adlint/cpp/macro.rb, line 167
def parse_one_argument(toks, idx)
  arg = []
  paren_depth = 0
  while tok = toks[idx]
    case tok.value
    when "("
      arg.push(tok)
      paren_depth += 1
    when ")"
      paren_depth -= 1
      if paren_depth >= 0
        arg.push(tok)
      else
        return arg, idx, true
      end
    when ","
      if paren_depth > 0
        arg.push(tok)
      else
        return arg, idx + 1, false
      end
    when "\n"
      ;
    else
      arg.push(tok)
    end
    idx += 1
  end
  return arg, idx, true # NOTREACHED
end
stringize_argument(arg, expansion_loc, macro_tbl) click to toggle source
# File lib/adlint/cpp/macro.rb, line 267
def stringize_argument(arg, expansion_loc, macro_tbl)
  # NOTE: The ISO C99 standard says;
  #
  # 6.10.3.2 The # operator
  #
  # Constraints
  #
  # 1 Each # preprocessing token in the replacement list for a
  #   function-like macro shall be followed by a parameter as the next
  #   preprocessing token in the replacement list.
  #
  # Semantics
  #
  # 2 If, in the replacement list, a parameter is immediately proceeded by
  #   a # preprocessing token, both are replaced by a single character
  #   string literal preprocessing token that contains the spelling of the
  #   preprocessing token sequence for the corresponding argument.  Each
  #   occurrence of white space between the argument's preprocessing tokens
  #   becomes a single space character in the character string literal.
  #   White space before the first preprocessing token and after the last
  #   preprocessing token composing the argument is deleted.  Otherwise,
  #   the original spelling of each preprocessing token in the argument is
  #   retained in the character string literal, except for special handling
  #   for producing the spelling of string literals and character
  #   constants: a \ character is inserted before each " and \ character of
  #   a character constant or string literal (including the delimiting "
  #   characters), except that it is implementation-defined whether a \
  #   character is inserted before the \ character beginning of universal
  #   character name.  If the replacement that results is not a valid
  #   character string literal, the behavior is undefined.  The character
  #   string literal corresponding to an empty argument is "".  The order
  #   of evaluation of # and ## operators is unspecified.
  #
  # NOTE: This code does not concern about contents of the string literal.
  #       But, it is enough for analysis.

  str = arg.map { |tok| tok.value }.join
  if str =~ /((:?\\*))\\\z/ && $1.length.even?
    str.chop!
    macro_tbl.notify_last_backslash_ignored(arg.last)
  end

  ReplacedToken.new(:PP_TOKEN, "\"#{str.gsub(/["\\]/) { "\\" + $& }}\"",
                    expansion_loc, :STRING_LITERAL, false)
end
substitute_argument(param_tok, nxt_tok, arg, loc, rslt_toks, macro_tbl, repl_ctxt) click to toggle source
# File lib/adlint/cpp/macro.rb, line 240
def substitute_argument(param_tok, nxt_tok, arg, loc, rslt_toks, macro_tbl,
                        repl_ctxt)
  # NOTE: The ISO C99 standard says;
  #
  # 6.10.3.1 Argument substitution
  #
  # 1 After the arguments for the invocation of a function-like macro have
  #   been identified, argument substitution take place.  A parameter in
  #   the replacement list, unless proceeded by a # or ## preprocessing
  #   token or followed by a ## preprocessing token, is replaced by the
  #   corresponding argument after all macros contained therein have been
  #   expanded.  Before being substituted, each argument's preprocessing
  #   tokens are completely macro replaced as if they formed the rest of
  #   the preprocessing file; no other preprocessing tokens are available.

  if nxt_tok && nxt_tok.value == "##"
    rslt_toks.concat(arg.map { |tok|
      ReplacedToken.new(tok.type, tok.value, loc, tok.type_hint, false)
    })
  else
    macro_tbl.replace(arg, repl_ctxt)
    rslt_toks.concat(arg.map { |tok|
      ReplacedToken.new(tok.type, tok.value, loc, tok.type_hint, true)
    })
  end
end
unlex_tokens(arg_toks) click to toggle source
# File lib/adlint/cpp/macro.rb, line 368
def unlex_tokens(arg_toks)
  # NOTE: To regenerate source string from pp_tokens in preparation for
  #       the argument substitution.

  # 6.10.3 Macro replacement
  #
  # Semantics
  #
  # 11 The sequence of preprocessing tokens bounded by the outside-most
  #    matching parentheses forms the list of arguments for the
  #    function-like macro.  The individual arguments within the list are
  #    separated by comma preprocessing tokens, but comma preprocessing
  #    tokens between matching inner parentheses do not separate arguments.
  #    If there are sequences of preprocessing tokens within the list of
  #    arguments that would otherwise act as preprocessing directives, the
  #    behavior is undefined.

  arg_toks.each_cons(2).reduce(arg_toks.first.value) do |str, (lhs, rhs)|
    lhs_loc, rhs_loc = lhs.location, rhs.location
    if lhs_loc.line_no == rhs_loc.line_no
      tok_gap = rhs_loc.column_no - lhs_loc.column_no - lhs.value.length
      str + " " * [tok_gap, 0].max + rhs.value
    else
      "#{str} #{rhs.value}"
    end
  end
end