Exemplo n.º 1
0
  def check_required_kwargs(self, lint_ctx, required_kwargs):
    for kwargnode in self.kwarg_groups:
      required_kwargs.pop(get_normalized_kwarg(kwargnode.keyword.token), None)

    if required_kwargs:
      location = self.get_location()
      for token in self.get_semantic_tokens():
        location = token.get_location()
        break

      # NOTE(josh): inner sorted() needed for stable sort.
      missing_kwargs = sorted(
          (lintid, word) for word, lintid in sorted(required_kwargs.items()))
      for lintid, word in missing_kwargs:
        lint_ctx.record_lint(lintid, word, location=location)
Exemplo n.º 2
0
def parse_add_custom_command(ctx, tokens, breakstack):
    """
  There are two forms of `add_custom_command`. This is the dispatcher between
  the two forms.
  """
    descriminator_token = get_first_semantic_token(tokens)
    if (descriminator_token is None
            or descriminator_token.type is TokenType.RIGHT_PAREN):
        location = ()
        if tokens:
            location = tokens[0].get_location()
        logger.warning("Invalid empty file() command at %s", location)
        ctx.lint_ctx.record_lint("E1120", location=location)
        return StandardArgTree.parse(ctx,
                                     tokens,
                                     npargs='*',
                                     kwargs={},
                                     flags=[],
                                     breakstack=breakstack)

    if descriminator_token.type is TokenType.DEREF:
        ctx.lint_ctx.record_lint("C0114",
                                 location=descriminator_token.get_location())
        return parse_add_custom_command_standard(ctx, tokens, breakstack)

    descriminator_word = get_normalized_kwarg(descriminator_token)
    if descriminator_word == "TARGET":
        return parse_add_custom_command_events(ctx, tokens, breakstack)
    if descriminator_word == "OUTPUT":
        return parse_add_custom_command_standard(ctx, tokens, breakstack)

    logger.warning("Indeterminate form of add_custom_command \"%s\" at %s",
                   descriminator_word, descriminator_token.location())
    ctx.lint_ctx.record_lint("E1126",
                             location=descriminator_token.get_location())
    return parse_add_custom_command_standard(ctx, tokens, breakstack)
Exemplo n.º 3
0
  def parse(cls, ctx, tokens, npargs, kwargs, flags, breakstack):
    """
    Standard parser for the commands in the form of::

        command_name(parg1 parg2 parg3...
                    KEYWORD1 kwarg1 kwarg2...
                    KEYWORD2 kwarg3 kwarg4...
                    FLAG1 FLAG2 FLAG3)

    The parser starts off as a positional parser. If a keyword or flag is
    encountered the positional parser is popped off the parse stack. If it was
    a keyword then the keyword parser is pushed on the parse stack. If it was
    a flag than a new flag parser is pushed onto the stack.
    """

    tree = cls()

    # If it is a whitespace token then put it directly in the parse tree at
    # the current depth
    while tokens and tokens[0].type in WHITESPACE_TOKENS:
      tree.children.append(tokens.pop(0))
      continue

    flags = [flag.upper() for flag in flags]
    kwarg_breakstack = breakstack + [KwargBreaker(list(kwargs.keys()) + flags)]
    positional_breakstack = breakstack + [KwargBreaker(list(kwargs.keys()))]

    while tokens:
      # Break if the next token belongs to a parent parser, i.e. if it
      # matches a keyword argument of something higher in the stack, or if
      # it closes a parent group.
      if should_break(tokens[0], breakstack):
        break

      # If it is a whitespace token then put it directly in the parse tree at
      # the current depth
      if tokens[0].type in WHITESPACE_TOKENS:
        tree.children.append(tokens.pop(0))
        continue

      # If it's a comment, then add it at the current depth
      if tokens[0].type in (lexer.TokenType.COMMENT,
                            lexer.TokenType.BRACKET_COMMENT):
        child = TreeNode(NodeType.COMMENT)
        tree.children.append(child)
        child.children.append(tokens.pop(0))
        continue

      ntokens = len(tokens)
      # NOTE(josh): each flag is also stored in kwargs as with a positional
      # parser of size zero. This is a legacy thing that should be removed, but
      # for now just make sure we check flags first.
      word = get_normalized_kwarg(tokens[0])
      if word in kwargs:
        subtree = KeywordGroupNode.parse(
            ctx, tokens, word, kwargs[word], kwarg_breakstack)
        tree.kwarg_groups.append(subtree)
      else:
        subtree = PositionalGroupNode.parse(
            ctx, tokens, npargs, flags, positional_breakstack)
        tree.parg_groups.append(subtree)

      assert len(tokens) < ntokens, "parsed an empty subtree"
      tree.children.append(subtree)
    return tree
Exemplo n.º 4
0
  def parse(cls, ctx, tokens, breakstack):
    """
    Parser for the commands that take conditional arguments. Similar to the
    standard parser but it understands parentheses and can generate
    parenthentical groups::

        while(CONDITION1 AND (CONDITION2 OR CONDITION3)
              OR (CONDITION3 AND (CONDITION4 AND CONDITION5)
              OR CONDITION6)
    """
    kwargs = {
        'AND': cls.parse,
        'OR': cls.parse
    }
    flags = list(CONDITIONAL_FLAGS)
    tree = cls()

    # If it is a whitespace token then put it directly in the parse tree at
    # the current depth
    while tokens and tokens[0].type in WHITESPACE_TOKENS:
      tree.children.append(tokens.pop(0))
      continue

    flags = [flag.upper() for flag in flags]
    breaker = KwargBreaker(list(kwargs.keys()))
    child_breakstack = breakstack + [breaker]

    while tokens:
      # Break if the next token belongs to a parent parser, i.e. if it
      # matches a keyword argument of something higher in the stack, or if
      # it closes a parent group.
      if should_break(tokens[0], breakstack):
        break

      # If it is a whitespace token then put it directly in the parse tree at
      # the current depth
      if tokens[0].type in WHITESPACE_TOKENS:
        tree.children.append(tokens.pop(0))
        continue

      # If it's a comment, then add it at the current depth
      if tokens[0].type in (lexer.TokenType.COMMENT,
                            lexer.TokenType.BRACKET_COMMENT):
        child = TreeNode(NodeType.COMMENT)
        tree.children.append(child)
        child.children.append(tokens.pop(0))
        continue

      # If this is the start of a parenthetical group, then parse the group
      if tokens[0].type == lexer.TokenType.LEFT_PAREN:
        subtree = ParenGroupNode.parse(ctx, tokens, breakstack)
        tree.children.append(subtree)
        continue

      ntokens = len(tokens)
      word = get_normalized_kwarg(tokens[0])
      if word in kwargs:
        subtree = KeywordGroupNode.parse(
            ctx, tokens, word, kwargs[word], child_breakstack)
        assert len(tokens) < ntokens, "parsed an empty subtree"
        tree.children.append(subtree)
        continue

      # Otherwise is it is a positional argument, so add it to the tree as such
      child = PositionalGroupNode.parse(
          ctx, tokens, '+', flags, child_breakstack)
      # token = tokens.pop(0)
      # if get_normalized_kwarg(token) in flags:
      #   child = TreeNode(NodeType.FLAG)
      # else:
      #   child = TreeNode(NodeType.ARGUMENT)

      # child.children.append(token)
      # consume_trailing_comment(child, tokens)
      tree.children.append(child)

    return tree
Exemplo n.º 5
0
  def parse(cls, ctx, tokens, npargs, flags, breakstack, sortable=False):
    """
    Parse a continuous sequence of `npargs` positional arguments. If npargs is
    an integer we will consume exactly that many arguments. If it is not an
    integer then it is a string meaning:

    * "?": zero or one
    * "*": zero or more
    * "+": one or more
    """

    tree = cls(sortable=sortable)
    tree.spec = PositionalSpec(npargs, flags)
    nconsumed = 0

    # Strip off any preceeding whitespace (note that in most cases this has
    # already been done but in some cases (such ask kwarg subparser) where
    # it hasn't
    while tokens and tokens[0].type in WHITESPACE_TOKENS:
      tree.children.append(tokens.pop(0))

    # If the first non-whitespace token is a cmake-format tag annotating
    # sortability, then parse it out here and record the annotation
    if tokens and get_tag(tokens[0]) in ("sortable", "sort"):
      tree.sortable = True
    elif tokens and get_tag(tokens[0]) in ("unsortable", "unsort"):
      tree.sortable = False

    while tokens:
      # Break if we have consumed   enough positional arguments
      if pargs_are_full(npargs, nconsumed):
        break

      # Break if the next token belongs to a parent parser, i.e. if it
      # matches a keyword argument of something higher in the stack, or if
      # it closes a parent group.
      if should_break(tokens[0], breakstack):
        # NOTE(josh): if npargs is an exact number of arguments, then we
        # shouldn't break on kwarg match from a parent parser. Instead, we
        # should consume the token. This is a hack to deal with
        # ```install(RUNTIME COMPONENT runtime)``. In this case the second
        # occurance of "runtime" should not match the ``RUNTIME`` keyword
        # and should not break the positional parser.
        # TODO(josh): this is kind of hacky because it will force the positional
        # parser to consume a right parenthesis and will lead to parse errors
        # in the event of a missing positional argument. Such errors will be
        # difficult to debug for the user.
        if not npargs_is_exact(npargs):
          break

        if tokens[0].type == lexer.TokenType.RIGHT_PAREN:
          break

      # If this is the start of a parenthetical group, then parse the group
      # NOTE(josh): syntatically this probably shouldn't be allowed here, but
      # cmake seems to accept it so we probably should too.
      if tokens[0].type == lexer.TokenType.LEFT_PAREN:
        subtree = ParenGroupNode.parse(ctx, tokens, breakstack)
        tree.children.append(subtree)
        continue

      # If it is a whitespace token then put it directly in the parse tree at
      # the current depth
      if tokens[0].type in WHITESPACE_TOKENS:
        tree.children.append(tokens.pop(0))
        continue

      # If it's a comment token not associated with an argument, then put it
      # directly into the parse tree at the current depth
      if tokens[0].type in (lexer.TokenType.COMMENT,
                            lexer.TokenType.BRACKET_COMMENT):
        before = len(tokens)
        child = CommentNode.consume(ctx, tokens)
        assert len(tokens) < before, \
            "consume_comment didn't consume any tokens"
        tree.children.append(child)
        continue

      # Otherwise is it is a positional argument, so add it to the tree as such
      if get_normalized_kwarg(tokens[0]) in flags:
        child = TreeNode(NodeType.FLAG)
      else:
        child = TreeNode(NodeType.ARGUMENT)

      child.children.append(tokens.pop(0))
      CommentNode.consume_trailing(ctx, tokens, child)
      tree.children.append(child)
      nconsumed += 1

    return tree
Exemplo n.º 6
0
def parse_add_custom_target(ctx, tokens, breakstack):
    """
  ::
    add_custom_target(Name [ALL] [command1 [args1...]]
                      [COMMAND command2 [args2...] ...]
                      [DEPENDS depend depend depend ... ]
                      [BYPRODUCTS [files...]]
                      [WORKING_DIRECTORY dir]
                      [COMMENT comment]
                      [JOB_POOL job_pool]
                      [VERBATIM] [USES_TERMINAL]
                      [COMMAND_EXPAND_LISTS]
                      [SOURCES src1 [src2...]])

  :see: https://cmake.org/cmake/help/latest/command/add_custom_target.html
  """
    kwargs = {
        "BYPRODUCTS": PositionalParser("+"),
        "COMMAND": ShellCommandNode.parse,
        "COMMENT": PositionalParser(1),
        "DEPENDS": PositionalParser("+"),
        "JOB_POOL": PositionalParser(1),
        "SOURCES": PositionalParser("+"),
        "WORKING_DIRECTORY": PositionalParser(1),
    }

    required_kwargs = {
        # Required by convention
        "COMMENT": "C0113"
    }

    flags = ("VERBATIM", "USES_TERMINAL", "COMMAND_EXPAND_LISTS")
    tree = ArgGroupNode()

    # If it is a whitespace token then put it directly in the parse tree at
    # the current depth
    while tokens and tokens[0].type in WHITESPACE_TOKENS:
        tree.children.append(tokens.pop(0))
        continue

    breaker = KwargBreaker(list(kwargs.keys()) + list(flags))
    child_breakstack = breakstack + [breaker]

    nametree = None
    state = "name"

    while tokens:
        # Break if the next token belongs to a parent parser, i.e. if it
        # matches a keyword argument of something higher in the stack, or if
        # it closes a parent group.
        if should_break(tokens[0], breakstack):
            break

        # If it is a whitespace token then put it directly in the parse tree at
        # the current depth
        if tokens[0].type in WHITESPACE_TOKENS:
            tree.children.append(tokens.pop(0))
            continue

        # If it's a comment, then add it at the current depth
        if tokens[0].type in (TokenType.COMMENT, TokenType.BRACKET_COMMENT):
            child = TreeNode(NodeType.COMMENT)
            tree.children.append(child)
            child.children.append(tokens.pop(0))
            continue

        ntokens = len(tokens)
        if state == "name":
            next_semantic = get_first_semantic_token(tokens[1:])
            if (next_semantic is not None
                    and get_normalized_kwarg(next_semantic) == "ALL"):
                npargs = 2
            else:
                npargs = 1

            nametree = PositionalGroupNode.parse(ctx, tokens, npargs, ["ALL"],
                                                 child_breakstack)
            assert len(tokens) < ntokens
            tree.children.append(nametree)
            state = "first-command"
            continue

        word = get_normalized_kwarg(tokens[0])
        if state == "first-command":
            if not (word in kwargs or word in flags):
                subtree = PositionalGroupNode.parse(ctx, tokens, '+', [],
                                                    child_breakstack)
                tree.children.append(subtree)
                assert len(tokens) < ntokens
            state = "kwargs"
            continue

        if word in flags:
            subtree = FlagGroupNode.parse(
                ctx, tokens, flags,
                breakstack + [KwargBreaker(list(kwargs.keys()))])
            assert len(tokens) < ntokens
            tree.children.append(subtree)
            continue

        if word in kwargs:
            required_kwargs.pop(word, None)
            subtree = KeywordGroupNode.parse(ctx, tokens, word, kwargs[word],
                                             child_breakstack)
            assert len(tokens) < ntokens
            tree.children.append(subtree)
            continue

        ctx.lint_ctx.record_lint("E1122", location=tokens[0].get_location())
        logger.warning("Unexpected positional argument %s at %s",
                       tokens[0].spelling, tokens[0].location())
        subtree = PositionalGroupNode.parse(ctx, tokens, '+', [],
                                            child_breakstack)
        assert len(tokens) < ntokens
        tree.children.append(subtree)
        continue

    if required_kwargs:
        location = ()
        for token in tree.get_semantic_tokens():
            location = token.get_location()
            break

        missing_kwargs = sorted(
            (lintid, word) for word, lintid in sorted(required_kwargs.items()))
        for lintid, word in missing_kwargs:
            ctx.lint_ctx.record_lint(lintid, word, location=location)
    return tree
Exemplo n.º 7
0
    def parse2(cls, ctx, tokens, cmdspec, kwargs, breakstack):
        """
    Standard parser for the commands in the form of::

        command_name(parg1 parg2 parg3...
                    KEYWORD1 kwarg1 kwarg2...
                    KEYWORD2 kwarg3 kwarg4...
                    FLAG1 FLAG2 FLAG3)
    The parser starts off as a positional parser. If a keyword or flag is
    encountered the positional parser is popped off the parse stack. If it was
    a keyword then the keyword parser is pushed on the parse stack. If it was
    a flag than a new flag parser is pushed onto the stack.
    """

        # NOTE(josh): we will pop things off this list, so let's make a copy
        pargspecs = list(cmdspec.pargs)
        tree = cls()
        tree.cmdspec = cmdspec

        # If it is a whitespace token then put it directly in the parse tree at
        # the current depth
        while tokens and tokens[0].type in WHITESPACE_TOKENS:
            tree.children.append(tokens.pop(0))
            continue

        # NOTE(josh): if there is only one non-exact legacy specification then we
        # reuse that specification for any additional positional arguments that we
        # pick up. This is to maintain the current/legacy behavior of simple
        # positional argument specifications
        # TODO(josh): double check the reasoning for this. I think it might be
        # mistaken and unnecessary
        default_spec = DEFAULT_PSPEC
        if (len(pargspecs) == 1 and pargspecs[0].legacy
                and not npargs_is_exact(pargspecs[0].nargs)):
            default_spec = pargspecs.pop(0)

        all_flags = list(default_spec.flags)
        for pspec in pargspecs:
            all_flags.extend(pspec.flags)

        kwarg_breakstack = breakstack + [
            KwargBreaker(list(kwargs.keys()) + all_flags)
        ]

        while tokens:
            # If it is a whitespace token then put it directly in the parse tree at
            # the current depth
            if tokens[0].type in WHITESPACE_TOKENS:
                tree.children.append(tokens.pop(0))
                continue

            # If it's a comment, then add it at the current depth
            if tokens[0].type in (lexer.TokenType.COMMENT,
                                  lexer.TokenType.BRACKET_COMMENT):
                if comment_belongs_up_tree(ctx, tokens, tree, breakstack):
                    break
                tree.children.append(CommentNode.consume(ctx, tokens))
                continue

            # If it's a sentinel comment, then add it at the current depth
            if tokens[0].type in (lexer.TokenType.FORMAT_OFF,
                                  lexer.TokenType.FORMAT_ON):
                tree.children.append(OnOffNode.consume(ctx, tokens))
                continue

            # Break if the next token belongs to a parent parser, i.e. if it
            # matches a keyword argument of something higher in the stack, or if
            # it closes a parent group.
            if should_break(tokens[0], breakstack):
                # NOTE(josh): if spec.nargs is an exact number of arguments, then we
                # shouldn't break on kwarg match from a parent parser. Instead, we
                # should consume that many tokens. This is a hack to deal with
                # ```install(RUNTIME COMPONENT runtime)``. In this case the second
                # occurance of "runtime" should not match the ``RUNTIME`` keyword
                # and should not break the positional parser.
                # TODO(josh): this is kind of hacky because it will force the positional
                # parser to consume a right parenthesis and will lead to parse errors
                # in the event of a missing positional argument. Such errors will be
                # difficult to debug for the user.
                if pargspecs:
                    pspec = pargspecs[0]
                else:
                    pspec = default_spec

                if not npargs_is_exact(pspec.nargs) or pspec.nargs == 0:
                    break

            ntokens = len(tokens)
            word = get_normalized_kwarg(tokens[0])
            if word in kwargs:
                with ctx.pusharg(tree):
                    subtree = KeywordGroupNode.parse(ctx, tokens, word,
                                                     kwargs[word],
                                                     kwarg_breakstack)
                tree.kwarg_groups.append(subtree)
            else:
                if pargspecs:
                    pspec = pargspecs.pop(0)
                else:
                    pspec = default_spec

                other_flags = []
                for otherspec in pargspecs:
                    for flag in otherspec.flags:
                        if flag in pspec.flags:
                            continue
                        other_flags.append(flag)
                positional_breakstack = breakstack + [
                    KwargBreaker(list(kwargs.keys()) + other_flags)
                ]

                with ctx.pusharg(tree):
                    subtree = PositionalGroupNode.parse2(
                        ctx, tokens, pspec, positional_breakstack)
                    tree.parg_groups.append(subtree)

            if len(tokens) >= ntokens:
                raise InternalError(
                    "parsed an empty subtree at {}:\n  {}\n pspec: {}".format(
                        tokens[0], dump_tree_tostr([tree]), pspec))
            tree.children.append(subtree)
        return tree
Exemplo n.º 8
0
    def parse(cls, ctx, tokens, npargs, ntup, flags, breakstack):
        """Parse a continuous sequence of `npargs` positional argument pairs.
    If npargs is an integer we will consume exactly that many arguments.
    If it is not an integer then it is a string meaning:

    * "?": zero or one
    * "*": zero or more
    * "+": one or more
    """

        tree = cls()
        subtree = None
        active_depth = tree

        npargs_consumed = 0
        ntup_consumed = 0

        while tokens:
            # Break if we have consumed enough positional arguments
            if pargs_are_full(npargs, npargs_consumed):
                break

            # Break if the next token belongs to a parent parser, i.e. if it
            # matches a keyword argument of something higher in the stack, or if
            # it closes a parent group.
            if should_break(tokens[0], breakstack):
                break

            # Otherwise we will consume the token
            token = tokens.pop(0)

            # If it is a whitespace token then put it directly in the parse tree at
            # the current depth
            if token.type in WHITESPACE_TOKENS:
                active_depth.children.append(token)
                continue

            # If it's a comment token not associated with an argument, then put it
            # directly into the parse tree at the current depth
            if token.type in (lexer.TokenType.COMMENT,
                              lexer.TokenType.BRACKET_COMMENT):
                child = CommentNode()
                tree.children.append(child)
                child.children.append(token)
                continue

            if subtree is None:
                subtree = PositionalGroupNode()
                tree.children.append(subtree)
                ntup_consumed = 0

            # Otherwise is it is a positional argument, so add it to the tree as such
            if get_normalized_kwarg(token) in flags:
                child = TreeNode(NodeType.FLAG)
            else:
                child = TreeNode(NodeType.ARGUMENT)

            child.children.append(token)
            CommentNode.consume_trailing(ctx, tokens, child)
            subtree.children.append(child)
            ntup_consumed += 1

            if ntup_consumed >= ntup:
                npargs_consumed += 1
                subtree = None

        return tree
Exemplo n.º 9
0
def parse_add_library_standard(ctx, tokens, breakstack, sortable):
    """
  ::

    add_library(<name> [STATIC | SHARED | MODULE]
               [EXCLUDE_FROM_ALL]
               source1 [source2 ...])

  :see: https://cmake.org/cmake/help/v3.0/command/add_library.html
  """
    # pylint: disable=too-many-statements

    parsing_name = 1
    parsing_type = 2
    parsing_flag = 3
    parsing_sources = 4

    tree = TreeNode(NodeType.ARGGROUP)

    # If it is a whitespace token then put it directly in the parse tree at
    # the current depth
    while tokens and tokens[0].type in WHITESPACE_TOKENS:
        tree.children.append(tokens.pop(0))
        continue

    state_ = parsing_name
    parg_group = None
    src_group = None
    active_depth = tree

    flags = ("STATIC", "SHARED", "MODULE", "OBJECT")

    while tokens:
        # This parse function breakson the first right paren, since parenthetical
        # groups are not allowed. A parenthesis might exist in a filename, but
        # if so that filename should be quoted so it wont show up as a RIGHT_PAREN
        # token.
        if tokens[0].type is TokenType.RIGHT_PAREN:
            break

        # If it is a whitespace token then put it directly in the parse tree at
        # the current depth
        if tokens[0].type in WHITESPACE_TOKENS:
            active_depth.children.append(tokens.pop(0))
            continue

        # If it's a comment token not associated with an argument, then put it
        # directly into the parse tree at the current depth
        if tokens[0].type in (TokenType.COMMENT, TokenType.BRACKET_COMMENT):
            if state_ > parsing_name:
                if get_tag(tokens[0]) in ("unsort", "unsortable"):
                    sortable = False
                elif get_tag(tokens[0]) in ("sort", "sortable"):
                    sortable = True
            child = TreeNode(NodeType.COMMENT)
            active_depth.children.append(child)
            child.children.append(tokens.pop(0))
            continue

        if state_ is parsing_name:
            token = tokens.pop(0)
            parg_group = PositionalGroupNode()
            parg_group.spec = PositionalSpec("+")
            active_depth = parg_group
            tree.children.append(parg_group)
            child = TreeNode(NodeType.ARGUMENT)
            child.children.append(token)
            CommentNode.consume_trailing(ctx, tokens, child)
            parg_group.children.append(child)
            state_ += 1
        elif state_ is parsing_type:
            if get_normalized_kwarg(tokens[0]) in flags:
                token = tokens.pop(0)
                child = TreeNode(NodeType.FLAG)
                child.children.append(token)
                CommentNode.consume_trailing(ctx, tokens, child)
                parg_group.children.append(child)
            state_ += 1
        elif state_ is parsing_flag:
            if get_normalized_kwarg(tokens[0]) == "EXCLUDE_FROM_ALL":
                token = tokens.pop(0)
                child = TreeNode(NodeType.FLAG)
                child.children.append(token)
                CommentNode.consume_trailing(ctx, tokens, child)
                parg_group.children.append(child)
            state_ += 1
            src_group = PositionalGroupNode(sortable=sortable,
                                            tags=["file-list"])
            src_group.spec = PositionalSpec("+")
            active_depth = src_group
            tree.children.append(src_group)
        elif state_ is parsing_sources:
            token = tokens.pop(0)
            child = TreeNode(NodeType.ARGUMENT)
            child.children.append(token)
            CommentNode.consume_trailing(ctx, tokens, child)
            src_group.children.append(child)

            if only_comments_and_whitespace_remain(tokens, breakstack):
                active_depth = tree

    return tree
Exemplo n.º 10
0
  def parse2(cls, ctx, tokens, pargspecs, kwargs, breakstack):
    """
    Standard parser for the commands in the form of::

        command_name(parg1 parg2 parg3...
                    KEYWORD1 kwarg1 kwarg2...
                    KEYWORD2 kwarg3 kwarg4...
                    FLAG1 FLAG2 FLAG3)
    The parser starts off as a positional parser. If a keyword or flag is
    encountered the positional parser is popped off the parse stack. If it was
    a keyword then the keyword parser is pushed on the parse stack. If it was
    a flag than a new flag parser is pushed onto the stack.
    """

    tree = cls()

    # If it is a whitespace token then put it directly in the parse tree at
    # the current depth
    while tokens and tokens[0].type in WHITESPACE_TOKENS:
      tree.children.append(tokens.pop(0))
      continue

    # NOTE(josh): if there is only one legacy specification then we reuse that
    # specification for any additional positional arguments that we pick up.
    # This is to maintain the current/legacy behavior of simple positional
    # argument specifications
    default_spec = DEFAULT_PSPEC
    if len(pargspecs) == 1 and pargspecs[0].legacy:
      default_spec = pargspecs.pop(0)

    all_flags = list(default_spec.flags)
    for pspec in pargspecs:
      all_flags.extend(pspec.flags)
    kwarg_breakstack = breakstack + [
        KwargBreaker(list(kwargs.keys()) + all_flags)]

    while tokens:
      # Break if the next token belongs to a parent parser, i.e. if it
      # matches a keyword argument of something higher in the stack, or if
      # it closes a parent group.
      if should_break(tokens[0], breakstack):
        break

      # If it is a whitespace token then put it directly in the parse tree at
      # the current depth
      if tokens[0].type in WHITESPACE_TOKENS:
        tree.children.append(tokens.pop(0))
        continue

      # If it's a comment, then add it at the current depth
      if tokens[0].type in (lexer.TokenType.COMMENT,
                            lexer.TokenType.BRACKET_COMMENT):
        if comment_belongs_up_tree(ctx, tokens, tree, breakstack):
          break
        tree.children.append(CommentNode.consume(ctx, tokens))
        continue

      # If it's a sentinel comment, then add it at the current depth
      if tokens[0].type in (lexer.TokenType.FORMAT_OFF,
                            lexer.TokenType.FORMAT_ON):
        tree.children.append(OnOffNode.consume(ctx, tokens))
        continue

      ntokens = len(tokens)
      word = get_normalized_kwarg(tokens[0])
      if word in kwargs:
        with ctx.pusharg(tree):
          subtree = KeywordGroupNode.parse(
              ctx, tokens, word, kwargs[word], kwarg_breakstack)
        tree.kwarg_groups.append(subtree)
      else:
        if pargspecs:
          pspec = pargspecs.pop(0)
        else:
          pspec = default_spec

        other_flags = []
        for otherspec in pargspecs:
          for flag in otherspec.flags:
            if flag in pspec.flags:
              continue
            other_flags.append(flag)
        positional_breakstack = breakstack + [
            KwargBreaker(list(kwargs.keys()) + other_flags)]

        with ctx.pusharg(tree):
          subtree = PositionalGroupNode.parse(
              ctx, tokens, pspec.nargs, pspec.flags, positional_breakstack)
          subtree.tags.extend(pspec.tags)
          tree.parg_groups.append(subtree)

      assert len(tokens) < ntokens, "parsed an empty subtree"
      tree.children.append(subtree)
    return tree
Exemplo n.º 11
0
def parse_add_executable_standard(ctx, tokens, breakstack, sortable):
  """
  ::

    add_executable(<name> [WIN32] [MACOSX_BUNDLE]
                   [EXCLUDE_FROM_ALL]
                   [source1] [source2 ...])

  :see: https://cmake.org/cmake/help/latest/command/add_executable.html#command:add_executable
  """
  # pylint: disable=too-many-statements

  parsing_name = 1
  parsing_flags = 2
  parsing_sources = 3

  tree = TreeNode(NodeType.ARGGROUP)

  # If it is a whitespace token then put it directly in the parse tree at
  # the current depth
  while tokens and tokens[0].type in WHITESPACE_TOKENS:
    tree.children.append(tokens.pop(0))
    continue

  state_ = parsing_name
  parg_group = None
  src_group = None
  active_depth = tree

  while tokens:
    # This parse function breaks on the first right paren, since parenthetical
    # groups are not allowed. A parenthesis might exist in a filename, but
    # if so that filename should be quoted so it wont show up as a RIGHT_PAREN
    # token.
    if tokens[0].type is lexer.TokenType.RIGHT_PAREN:
      break

    # If it is a whitespace token then put it directly in the parse tree at
    # the current depth
    if tokens[0].type in WHITESPACE_TOKENS:
      active_depth.children.append(tokens.pop(0))
      continue

    # If it's a comment token not associated with an argument, then put it
    # directly into the parse tree at the current depth
    if tokens[0].type in (lexer.TokenType.COMMENT,
                          lexer.TokenType.BRACKET_COMMENT):
      if state_ > parsing_name:
        if get_tag(tokens[0]) in ("unsort", "unsortable"):
          sortable = False
        elif get_tag(tokens[0]) in ("unsort", "unsortable"):
          sortable = True
      child = TreeNode(NodeType.COMMENT)
      active_depth.children.append(child)
      child.children.append(tokens.pop(0))
      continue

    if state_ is parsing_name:
      token = tokens.pop(0)
      parg_group = TreeNode(NodeType.PARGGROUP)
      active_depth = parg_group
      tree.children.append(parg_group)
      child = TreeNode(NodeType.ARGUMENT)
      child.children.append(token)
      CommentNode.consume_trailing(ctx, tokens, child)
      parg_group.children.append(child)
      state_ += 1
    elif state_ is parsing_flags:
      if get_normalized_kwarg(tokens[0]) in (
          "WIN32", "MACOSX_BUNDLE", "EXCLUDE_FROM_ALL"):
        token = tokens.pop(0)
        child = TreeNode(NodeType.FLAG)
        child.children.append(token)
        CommentNode.consume_trailing(ctx, tokens, child)
        parg_group.children.append(child)
      else:
        state_ += 1
        src_group = TreeNode(NodeType.PARGGROUP, sortable=sortable)
        active_depth = src_group
        tree.children.append(src_group)
    elif state_ is parsing_sources:
      token = tokens.pop(0)
      child = TreeNode(NodeType.ARGUMENT)
      child.children.append(token)
      CommentNode.consume_trailing(ctx, tokens, child)
      src_group.children.append(child)

      if only_comments_and_whitespace_remain(tokens, breakstack):
        active_depth = tree

  return tree
Exemplo n.º 12
0
def parse_set(ctx, tokens, breakstack):
    """
  ::

    set(<variable> <value>
        [[CACHE <type> <docstring> [FORCE]] | PARENT_SCOPE])

  :see: https://cmake.org/cmake/help/v3.0/command/set.html?
  """
    tree = SetFnNode()

    # If it is a whitespace token then put it directly in the parse tree at
    # the current depth
    while tokens and tokens[0].type in WHITESPACE_TOKENS:
        tree.children.append(tokens.pop(0))
        continue

    kwargs = {"CACHE": PositionalParser('2+', flags=["FORCE"])}
    flags = ["PARENT_SCOPE"]
    kwarg_breakstack = breakstack + [KwargBreaker(list(kwargs.keys()) + flags)]
    positional_breakstack = breakstack + [KwargBreaker(list(kwargs.keys()))]

    ntokens = len(tokens)
    subtree = PositionalGroupNode.parse(ctx, tokens, 1, flags,
                                        positional_breakstack)
    assert len(tokens) < ntokens
    tree.children.append(subtree)
    for token in subtree.get_semantic_tokens():
        tree.varname = token
        break

    while tokens:
        # Break if the next token belongs to a parent parser, i.e. if it
        # matches a keyword argument of something higher in the stack, or if
        # it closes a parent group.
        if should_break(tokens[0], breakstack):
            break

        # If it is a whitespace token then put it directly in the parse tree at
        # the current depth
        if tokens[0].type in WHITESPACE_TOKENS:
            tree.children.append(tokens.pop(0))
            continue

        # If it's a comment, then add it at the current depth
        if tokens[0].type in (lexer.TokenType.COMMENT,
                              lexer.TokenType.BRACKET_COMMENT):
            if not get_tag(tokens[0]) in ("sort", "sortable"):
                child = TreeNode(NodeType.COMMENT)
                tree.children.append(child)
                child.children.append(tokens.pop(0))
                continue

        ntokens = len(tokens)

        # NOTE(josh): each flag is also stored in kwargs as with a positional parser
        # of size zero. This is a legacy thing that should be removed, but for now
        # just make sure we check flags first.
        word = get_normalized_kwarg(tokens[0])
        if word == "CACHE":
            subtree = KeywordGroupNode.parse(ctx, tokens, word, kwargs[word],
                                             kwarg_breakstack)
            cache_tokens = subtree.get_semantic_tokens()
            cache_tokens.pop(0)
            cache_args = [None, None, False]
            for idx, token in enumerate(cache_tokens[:2]):
                cache_args[idx] = token.spelling

            if not cache_tokens:
                ctx.lint_ctx.record_lint("E1120",
                                         "<variable>",
                                         location=tokens[0].get_location())
            elif len(cache_tokens) < 2:
                ctx.lint_ctx.record_lint("E1120",
                                         "<value>",
                                         location=tokens[0].get_location())
            elif len(cache_tokens) < 3:
                pass
            elif len(cache_tokens) > 4:
                ctx.lint_ctx.record_lint(
                    "E1121", location=cache_tokens[2].get_location())
            else:
                if cache_tokens[2].spelling.upper() == "FORCE":
                    cache_args[2] = True
                else:
                    ctx.lint_ctx.record_lint(
                        "E1121", location=cache_tokens[2].get_location())
            tree.cache = CacheTuple(*cache_args)

        elif word == "PARENT_SCOPE":
            subtree = PositionalGroupNode.parse(ctx, tokens, '+',
                                                ["PARENT_SCOPE"],
                                                positional_breakstack)
            tree.parent_scope = True
        else:
            subtree = PositionalGroupNode.parse(ctx, tokens, '+', [],
                                                kwarg_breakstack)
            tree.value_group = subtree

        assert len(tokens) < ntokens
        tree.children.append(subtree)
    return tree
Exemplo n.º 13
0
def parse_install_targets(ctx, tokens, breakstack):
  """
  ::

    install(TARGETS targets... [EXPORT <export-name>]
            [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
              PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
             [DESTINATION <dir>]
             [PERMISSIONS permissions...]
             [CONFIGURATIONS [Debug|Release|...]]
             [COMPONENT <component>]
             [NAMELINK_COMPONENT <component>]
             [OPTIONAL] [EXCLUDE_FROM_ALL]
             [NAMELINK_ONLY|NAMELINK_SKIP]
            ] [...]
            [INCLUDES DESTINATION [<dir> ...]]
            )

  :see: https://cmake.org/cmake/help/v3.14/command/install.html#targets
  """
  kwargs = {
      "TARGETS": PositionalParser('+'),
      "EXPORT": PositionalParser(1),
      "INCLUDES": PositionalParser('+', flags=["DESTINATION"]),
      # Common kwargs
      "DESTINATION": PositionalParser(1),
      "PERMISSIONS": PositionalParser('+'),
      "CONFIGURATIONS": PositionalParser('+'),
      "COMPONENT": PositionalParser(1),
      "NAMELINK_COMPONENT": PositionalParser(1),
  }
  flags = (
      "OPTIONAL",
      "EXCLUDE_FROM_ALL",
      "NAMELINK_ONLY",
      "NAMELINK_SKIP"
  )
  designated_kwargs = (
      "ARCHIVE", "LIBRARY", "RUNTIME", "OBJECTS", "FRAMEWORK",
      "BUNDLE", "PRIVATE_HEADER", "PUBLIC_HEADER", "RESOURCE"
  )

  # NOTE(josh): from here on, code is essentially StandardArgTree.parse(),
  # except that
  # we cannot break on the common subset of kwargs in the breakstack because
  # they are valid kwargs for the subtrees (ARCHIVE, LIBRARY, etc) as well as
  # the primary tree
  tree = TreeNode(NodeType.ARGGROUP)

  # If it is a whitespace token then put it directly in the parse tree at
  # the current depth
  while tokens and tokens[0].type in WHITESPACE_TOKENS:
    tree.children.append(tokens.pop(0))
    continue

  # ARCHIVE, LIBRARY, RUNTIME, subtrees etc only break on the start of
  # another subtree, or on "INCLUDES DESTINATION"
  subtree_breakstack = breakstack + [KwargBreaker(
      list(designated_kwargs) + ["INCLUDES"]
  )]

  # kwargs at this tree depth break on other kwargs or flags
  kwarg_breakstack = breakstack + [KwargBreaker(
      list(kwargs.keys()) + list(designated_kwargs) + list(flags)
  )]

  # and flags at this depth break only on kwargs
  positional_breakstack = breakstack + [KwargBreaker(
      list(kwargs.keys()) + list(designated_kwargs)
  )]

  while tokens:
    # Break if the next token belongs to a parent parser, i.e. if it
    # matches a keyword argument of something higher in the stack, or if
    # it closes a parent group.
    if should_break(tokens[0], breakstack):
      break

    # If it is a whitespace token then put it directly in the parse tree at
    # the current depth
    if tokens[0].type in WHITESPACE_TOKENS:
      tree.children.append(tokens.pop(0))
      continue

    # If it's a comment, then add it at the current depth
    if tokens[0].type in (lexer.TokenType.COMMENT,
                          lexer.TokenType.BRACKET_COMMENT):
      child = TreeNode(NodeType.COMMENT)
      tree.children.append(child)
      child.children.append(tokens.pop(0))
      continue

    ntokens = len(tokens)
    # NOTE(josh): each flag is also stored in kwargs as with a positional parser
    # of size zero. This is a legacy thing that should be removed, but for now
    # just make sure we check flags first.
    word = get_normalized_kwarg(tokens[0])
    if word in designated_kwargs:
      subtree = KeywordGroupNode.parse(
          ctx, tokens, word, parse_install_targets, subtree_breakstack)
    elif word in kwargs:
      subtree = KeywordGroupNode.parse(
          ctx, tokens, word, kwargs[word], kwarg_breakstack)
    else:
      subtree = PositionalGroupNode.parse(
          ctx, tokens, '+', flags, positional_breakstack)

    assert len(tokens) < ntokens
    tree.children.append(subtree)
  return tree